pax_global_header00006660000000000000000000000064147032342730014517gustar00rootroot0000000000000052 comment=9cf5701e6b6611b2c406639039a1bccd883db080 open-telemetry-opentelemetry-go-contrib-e5abccb/000077500000000000000000000000001470323427300222515ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.codespellignore000066400000000000000000000000071470323427300254250ustar00rootroot00000000000000ot fo open-telemetry-opentelemetry-go-contrib-e5abccb/.codespellrc000066400000000000000000000004201470323427300245450ustar00rootroot00000000000000# https://github.com/codespell-project/codespell [codespell] builtin = clear,rare,informal check-filenames = check-hidden = ignore-words = .codespellignore interactive = 1 skip = .git,go.mod,go.sum,go.work,go.work.sum,semconv,venv,.tools uri-ignore-words-list = * write = open-telemetry-opentelemetry-go-contrib-e5abccb/.gitattributes000066400000000000000000000001311470323427300251370ustar00rootroot00000000000000* text=auto eol=lf *.{cmd,[cC][mM][dD]} text eol=crlf *.{bat,[bB][aA][tT]} text eol=crlf open-telemetry-opentelemetry-go-contrib-e5abccb/.github/000077500000000000000000000000001470323427300236115ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE/000077500000000000000000000000001470323427300257745ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000007731470323427300304750ustar00rootroot00000000000000--- name: 'Bug report' about: Create a report of invalid behavior to help us improve title: '' labels: 'bug' --- ### Description A clear and concise description of what the bug is. ### Environment - OS: [e.g. iOS] - Architecture: [e.g. x86, i386] - Go Version: [e.g. 1.15] - `go.opentelemetry.io/contrib` version: [e.g. v0.14.0, 3c7face] ### Steps To Reproduce 1. Using this code ... 2. Run ... 3. See error ... ### Expected behavior A clear and concise description of what you expected to happen. open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE/bug_report_autoexporter.md000066400000000000000000000010521470323427300333050ustar00rootroot00000000000000--- name: '[autoexporter] Bug report' about: Create a report of invalid behavior about the autoexporter package to help us improve title: '' labels: 'bug, area: exporter' --- ### Description A clear and concise description of what the bug is. ### Environment - OS: [e.g. iOS] - Architecture: [e.g. x86, i386] - Go Version: [e.g. 1.15] - `autoexporter` version: [e.g. v0.14.0, 3c7face] ### Steps To Reproduce 1. Using this code ... 2. Run ... 3. See error ... ### Expected behavior A clear and concise description of what you expected to happen. open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE/bug_report_config.md000066400000000000000000000010261470323427300320120ustar00rootroot00000000000000--- name: '[config] Bug report' about: Create a report of invalid behavior about the config package to help us improve title: '' labels: 'bug, area: config' --- ### Description A clear and concise description of what the bug is. ### Environment - OS: [e.g. iOS] - Architecture: [e.g. x86, i386] - Go Version: [e.g. 1.15] - `config` version: [e.g. v0.14.0, 3c7face] ### Steps To Reproduce 1. Using this code ... 2. Run ... 3. See error ... ### Expected behavior A clear and concise description of what you expected to happen. bug_report_detector_aws_ec2.md000066400000000000000000000010601470323427300337000ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[detector: aws:ec2] Bug report' about: Create a report of invalid behavior about the ec2 package to help us improve title: '' labels: 'bug, area: detector, detector: aws:ec2' --- ### Description A clear and concise description of what the bug is. ### Environment - OS: [e.g. iOS] - Architecture: [e.g. x86, i386] - Go Version: [e.g. 1.15] - `ec2` version: [e.g. v0.14.0, 3c7face] ### Steps To Reproduce 1. Using this code ... 2. Run ... 3. See error ... ### Expected behavior A clear and concise description of what you expected to happen. bug_report_detector_aws_ecs.md000066400000000000000000000010601470323427300340010ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[detector: aws:ecs] Bug report' about: Create a report of invalid behavior about the ecs package to help us improve title: '' labels: 'bug, area: detector, detector: aws:ecs' --- ### Description A clear and concise description of what the bug is. ### Environment - OS: [e.g. iOS] - Architecture: [e.g. x86, i386] - Go Version: [e.g. 1.15] - `ecs` version: [e.g. v0.14.0, 3c7face] ### Steps To Reproduce 1. Using this code ... 2. Run ... 3. See error ... ### Expected behavior A clear and concise description of what you expected to happen. bug_report_detector_aws_eks.md000066400000000000000000000010601470323427300340110ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[detector: aws:eks] Bug report' about: Create a report of invalid behavior about the eks package to help us improve title: '' labels: 'bug, area: detector, detector: aws:eks' --- ### Description A clear and concise description of what the bug is. ### Environment - OS: [e.g. iOS] - Architecture: [e.g. x86, i386] - Go Version: [e.g. 1.15] - `eks` version: [e.g. v0.14.0, 3c7face] ### Steps To Reproduce 1. Using this code ... 2. Run ... 3. See error ... ### Expected behavior A clear and concise description of what you expected to happen. bug_report_detector_aws_lambda.md000066400000000000000000000010741470323427300344540ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[detector: aws:lambda] Bug report' about: Create a report of invalid behavior about the lambda package to help us improve title: '' labels: 'bug, area: detector, detector: aws:lambda' --- ### Description A clear and concise description of what the bug is. ### Environment - OS: [e.g. iOS] - Architecture: [e.g. x86, i386] - Go Version: [e.g. 1.15] - `lambda` version: [e.g. v0.14.0, 3c7face] ### Steps To Reproduce 1. Using this code ... 2. Run ... 3. See error ... ### Expected behavior A clear and concise description of what you expected to happen. open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE/bug_report_detector_gcp.md000066400000000000000000000010501470323427300332040ustar00rootroot00000000000000--- name: '[detector: gcp] Bug report' about: Create a report of invalid behavior about the gcp package to help us improve title: '' labels: 'bug, area: detector, detector: gcp' --- ### Description A clear and concise description of what the bug is. ### Environment - OS: [e.g. iOS] - Architecture: [e.g. x86, i386] - Go Version: [e.g. 1.15] - `gcp` version: [e.g. v0.14.0, 3c7face] ### Steps To Reproduce 1. Using this code ... 2. Run ... 3. See error ... ### Expected behavior A clear and concise description of what you expected to happen. open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE/bug_report_instrgen.md000066400000000000000000000011301470323427300323720ustar00rootroot00000000000000--- name: '[instrgen] Bug report' about: Create a report of invalid behavior about the instrgen package to help us improve title: '' labels: 'bug, area: instrgen' assignees: '@open-telemetry/go-instrumentation-approvers' --- ### Description A clear and concise description of what the bug is. ### Environment - OS: [e.g. iOS] - Architecture: [e.g. x86, i386] - Go Version: [e.g. 1.15] - `instrgen` version: [e.g. v0.14.0, 3c7face] ### Steps To Reproduce 1. Using this code ... 2. Run ... 3. See error ... ### Expected behavior A clear and concise description of what you expected to happen. bug_report_instrumentation_host.md000066400000000000000000000011011470323427300347600ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[instrumentation: host] Bug report' about: Create a report of invalid behavior about the host package to help us improve title: '' labels: 'bug, area: instrumentation, instrumentation: host' --- ### Description A clear and concise description of what the bug is. ### Environment - OS: [e.g. iOS] - Architecture: [e.g. x86, i386] - Go Version: [e.g. 1.15] - `host` version: [e.g. v0.14.0, 3c7face] ### Steps To Reproduce 1. Using this code ... 2. Run ... 3. See error ... ### Expected behavior A clear and concise description of what you expected to happen. bug_report_instrumentation_otelaws.md000066400000000000000000000011151470323427300354660ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[instrumentation: otelaws] Bug report' about: Create a report of invalid behavior about the otelaws package to help us improve title: '' labels: 'bug, area: instrumentation, instrumentation: otelaws' --- ### Description A clear and concise description of what the bug is. ### Environment - OS: [e.g. iOS] - Architecture: [e.g. x86, i386] - Go Version: [e.g. 1.15] - `otelaws` version: [e.g. v0.14.0, 3c7face] ### Steps To Reproduce 1. Using this code ... 2. Run ... 3. See error ... ### Expected behavior A clear and concise description of what you expected to happen. bug_report_instrumentation_otelecho.md000066400000000000000000000011211470323427300356070ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[instrumentation: otelecho] Bug report' about: Create a report of invalid behavior about the otelecho package to help us improve title: '' labels: 'bug, area: instrumentation, instrumentation: otelecho' --- ### Description A clear and concise description of what the bug is. ### Environment - OS: [e.g. iOS] - Architecture: [e.g. x86, i386] - Go Version: [e.g. 1.15] - `otelecho` version: [e.g. v0.14.0, 3c7face] ### Steps To Reproduce 1. Using this code ... 2. Run ... 3. See error ... ### Expected behavior A clear and concise description of what you expected to happen. bug_report_instrumentation_otelgin.md000066400000000000000000000011151470323427300354510ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[instrumentation: otelgin] Bug report' about: Create a report of invalid behavior about the otelgin package to help us improve title: '' labels: 'bug, area: instrumentation, instrumentation: otelgin' --- ### Description A clear and concise description of what the bug is. ### Environment - OS: [e.g. iOS] - Architecture: [e.g. x86, i386] - Go Version: [e.g. 1.15] - `otelgin` version: [e.g. v0.14.0, 3c7face] ### Steps To Reproduce 1. Using this code ... 2. Run ... 3. See error ... ### Expected behavior A clear and concise description of what you expected to happen. bug_report_instrumentation_otelgrpc.md000066400000000000000000000011211470323427300356240ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[instrumentation: otelgrpc] Bug report' about: Create a report of invalid behavior about the otelgrpc package to help us improve title: '' labels: 'bug, area: instrumentation, instrumentation: otelgrpc' --- ### Description A clear and concise description of what the bug is. ### Environment - OS: [e.g. iOS] - Architecture: [e.g. x86, i386] - Go Version: [e.g. 1.15] - `otelgrpc` version: [e.g. v0.14.0, 3c7face] ### Steps To Reproduce 1. Using this code ... 2. Run ... 3. See error ... ### Expected behavior A clear and concise description of what you expected to happen. bug_report_instrumentation_otelhttp.md000066400000000000000000000011211470323427300356500ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[instrumentation: otelhttp] Bug report' about: Create a report of invalid behavior about the otelhttp package to help us improve title: '' labels: 'bug, area: instrumentation, instrumentation: otelhttp' --- ### Description A clear and concise description of what the bug is. ### Environment - OS: [e.g. iOS] - Architecture: [e.g. x86, i386] - Go Version: [e.g. 1.15] - `otelhttp` version: [e.g. v0.14.0, 3c7face] ### Steps To Reproduce 1. Using this code ... 2. Run ... 3. See error ... ### Expected behavior A clear and concise description of what you expected to happen. bug_report_instrumentation_otelhttptrace.md000066400000000000000000000011451470323427300366750ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[instrumentation: otelhttptrace] Bug report' about: Create a report of invalid behavior about the otelhttptrace package to help us improve title: '' labels: 'bug, area: instrumentation, instrumentation: otelhttptrace' --- ### Description A clear and concise description of what the bug is. ### Environment - OS: [e.g. iOS] - Architecture: [e.g. x86, i386] - Go Version: [e.g. 1.15] - `otelhttptrace` version: [e.g. v0.14.0, 3c7face] ### Steps To Reproduce 1. Using this code ... 2. Run ... 3. See error ... ### Expected behavior A clear and concise description of what you expected to happen. bug_report_instrumentation_otellambda.md000066400000000000000000000011311470323427300361120ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[instrumentation: otellambda] Bug report' about: Create a report of invalid behavior about the otellambda package to help us improve title: '' labels: 'bug, area: instrumentation, instrumentation: otellambda' --- ### Description A clear and concise description of what the bug is. ### Environment - OS: [e.g. iOS] - Architecture: [e.g. x86, i386] - Go Version: [e.g. 1.15] - `otellambda` version: [e.g. v0.14.0, 3c7face] ### Steps To Reproduce 1. Using this code ... 2. Run ... 3. See error ... ### Expected behavior A clear and concise description of what you expected to happen. bug_report_instrumentation_otelmacaron.md000066400000000000000000000011351470323427300363160ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[instrumentation: otelmacaron] Bug report' about: Create a report of invalid behavior about the otelmacaron package to help us improve title: '' labels: 'bug, area: instrumentation, instrumentation: otelmacaron' --- ### Description A clear and concise description of what the bug is. ### Environment - OS: [e.g. iOS] - Architecture: [e.g. x86, i386] - Go Version: [e.g. 1.15] - `otelmacaron` version: [e.g. v0.14.0, 3c7face] ### Steps To Reproduce 1. Using this code ... 2. Run ... 3. See error ... ### Expected behavior A clear and concise description of what you expected to happen. bug_report_instrumentation_otelmongo.md000066400000000000000000000011251470323427300360140ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[instrumentation: otelmongo] Bug report' about: Create a report of invalid behavior about the otelmongo package to help us improve title: '' labels: 'bug, area: instrumentation, instrumentation: otelmongo' --- ### Description A clear and concise description of what the bug is. ### Environment - OS: [e.g. iOS] - Architecture: [e.g. x86, i386] - Go Version: [e.g. 1.15] - `otelmongo` version: [e.g. v0.14.0, 3c7face] ### Steps To Reproduce 1. Using this code ... 2. Run ... 3. See error ... ### Expected behavior A clear and concise description of what you expected to happen. bug_report_instrumentation_otelmux.md000066400000000000000000000011151470323427300355050ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[instrumentation: otelmux] Bug report' about: Create a report of invalid behavior about the otelmux package to help us improve title: '' labels: 'bug, area: instrumentation, instrumentation: otelmux' --- ### Description A clear and concise description of what the bug is. ### Environment - OS: [e.g. iOS] - Architecture: [e.g. x86, i386] - Go Version: [e.g. 1.15] - `otelmux` version: [e.g. v0.14.0, 3c7face] ### Steps To Reproduce 1. Using this code ... 2. Run ... 3. See error ... ### Expected behavior A clear and concise description of what you expected to happen. bug_report_instrumentation_otelrestful.md000066400000000000000000000011351470323427300363620ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[instrumentation: otelrestful] Bug report' about: Create a report of invalid behavior about the otelrestful package to help us improve title: '' labels: 'bug, area: instrumentation, instrumentation: otelrestful' --- ### Description A clear and concise description of what the bug is. ### Environment - OS: [e.g. iOS] - Architecture: [e.g. x86, i386] - Go Version: [e.g. 1.15] - `otelrestful` version: [e.g. v0.14.0, 3c7face] ### Steps To Reproduce 1. Using this code ... 2. Run ... 3. See error ... ### Expected behavior A clear and concise description of what you expected to happen. bug_report_instrumentation_runtime.md000066400000000000000000000011151470323427300354730ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[instrumentation: runtime] Bug report' about: Create a report of invalid behavior about the runtime package to help us improve title: '' labels: 'bug, area: instrumentation, instrumentation: runtime' --- ### Description A clear and concise description of what the bug is. ### Environment - OS: [e.g. iOS] - Architecture: [e.g. x86, i386] - Go Version: [e.g. 1.15] - `runtime` version: [e.g. v0.14.0, 3c7face] ### Steps To Reproduce 1. Using this code ... 2. Run ... 3. See error ... ### Expected behavior A clear and concise description of what you expected to happen. bug_report_propagator_autoprop.md000066400000000000000000000011031470323427300345710ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[propagator: autoprop] Bug report' about: Create a report of invalid behavior about the autoprop package to help us improve title: '' labels: 'bug, area: propagators, propagator: autoprop' --- ### Description A clear and concise description of what the bug is. ### Environment - OS: [e.g. iOS] - Architecture: [e.g. x86, i386] - Go Version: [e.g. 1.15] - `autoprop` version: [e.g. v0.14.0, 3c7face] ### Steps To Reproduce 1. Using this code ... 2. Run ... 3. See error ... ### Expected behavior A clear and concise description of what you expected to happen. open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE/bug_report_propagator_aws.md000066400000000000000000000010731470323427300335770ustar00rootroot00000000000000--- name: '[propagator: aws:xray] Bug report' about: Create a report of invalid behavior about the xray package to help us improve title: '' labels: 'bug, area: propagators, propagator: aws:xray' --- ### Description A clear and concise description of what the bug is. ### Environment - OS: [e.g. iOS] - Architecture: [e.g. x86, i386] - Go Version: [e.g. 1.15] - `xray` version: [e.g. v0.14.0, 3c7face] ### Steps To Reproduce 1. Using this code ... 2. Run ... 3. See error ... ### Expected behavior A clear and concise description of what you expected to happen. bug_report_propagator_aws_xray.md000066400000000000000000000010571470323427300345650ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[propagator: aws] Bug report' about: Create a report of invalid behavior about the aws package to help us improve title: '' labels: 'bug, area: propagators, propagator: aws' --- ### Description A clear and concise description of what the bug is. ### Environment - OS: [e.g. iOS] - Architecture: [e.g. x86, i386] - Go Version: [e.g. 1.15] - `aws` version: [e.g. v0.14.0, 3c7face] ### Steps To Reproduce 1. Using this code ... 2. Run ... 3. See error ... ### Expected behavior A clear and concise description of what you expected to happen. open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE/bug_report_propagator_b3.md000066400000000000000000000010531470323427300333070ustar00rootroot00000000000000--- name: '[propagator: b3] Bug report' about: Create a report of invalid behavior about the b3 package to help us improve title: '' labels: 'bug, area: propagators, propagator: b3' --- ### Description A clear and concise description of what the bug is. ### Environment - OS: [e.g. iOS] - Architecture: [e.g. x86, i386] - Go Version: [e.g. 1.15] - `b3` version: [e.g. v0.14.0, 3c7face] ### Steps To Reproduce 1. Using this code ... 2. Run ... 3. See error ... ### Expected behavior A clear and concise description of what you expected to happen. bug_report_propagator_jaeger.md000066400000000000000000000010731470323427300341630ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[propagator: jaeger] Bug report' about: Create a report of invalid behavior about the jaeger package to help us improve title: '' labels: 'bug, area: propagators, propagator: jaeger' --- ### Description A clear and concise description of what the bug is. ### Environment - OS: [e.g. iOS] - Architecture: [e.g. x86, i386] - Go Version: [e.g. 1.15] - `jaeger` version: [e.g. v0.14.0, 3c7face] ### Steps To Reproduce 1. Using this code ... 2. Run ... 3. See error ... ### Expected behavior A clear and concise description of what you expected to happen. bug_report_propagator_opencensus.md000066400000000000000000000011131470323427300351030ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[propagator: opencensus] Bug report' about: Create a report of invalid behavior about the opencensus package to help us improve title: '' labels: 'bug, area: propagators, propagator: opencensus' --- ### Description A clear and concise description of what the bug is. ### Environment - OS: [e.g. iOS] - Architecture: [e.g. x86, i386] - Go Version: [e.g. 1.15] - `opencensus` version: [e.g. v0.14.0, 3c7face] ### Steps To Reproduce 1. Using this code ... 2. Run ... 3. See error ... ### Expected behavior A clear and concise description of what you expected to happen. open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE/bug_report_propagator_ot.md000066400000000000000000000010531470323427300334250ustar00rootroot00000000000000--- name: '[propagator: ot] Bug report' about: Create a report of invalid behavior about the ot package to help us improve title: '' labels: 'bug, area: propagators, propagator: ot' --- ### Description A clear and concise description of what the bug is. ### Environment - OS: [e.g. iOS] - Architecture: [e.g. x86, i386] - Go Version: [e.g. 1.15] - `ot` version: [e.g. v0.14.0, 3c7face] ### Steps To Reproduce 1. Using this code ... 2. Run ... 3. See error ... ### Expected behavior A clear and concise description of what you expected to happen. bug_report_sampler_aws_xray.md000066400000000000000000000010421470323427300340440ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[sampler: aws:xray] Bug report' about: Create a report of invalid behavior about the xray package to help us improve title: '' labels: 'bug, sampler: aws:xray' --- ### Description A clear and concise description of what the bug is. ### Environment - OS: [e.g. iOS] - Architecture: [e.g. x86, i386] - Go Version: [e.g. 1.15] - `xray` version: [e.g. v0.14.0, 3c7face] ### Steps To Reproduce 1. Using this code ... 2. Run ... 3. See error ... ### Expected behavior A clear and concise description of what you expected to happen. bug_report_sampler_jaegerremote.md000066400000000000000000000010721470323427300346630ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[sampler: jaegerremote] Bug report' about: Create a report of invalid behavior about the jaegerremote package to help us improve title: '' labels: 'bug, sampler: jaegerremote' --- ### Description A clear and concise description of what the bug is. ### Environment - OS: [e.g. iOS] - Architecture: [e.g. x86, i386] - Go Version: [e.g. 1.15] - `jaegerremote` version: [e.g. v0.14.0, 3c7face] ### Steps To Reproduce 1. Using this code ... 2. Run ... 3. See error ... ### Expected behavior A clear and concise description of what you expected to happen. bug_report_sampler_probability_consistent.md000066400000000000000000000011121470323427300367760ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[sampler: probability:consistent] Bug report' about: Create a report of invalid behavior about the consistent package to help us improve title: '' labels: 'bug, sampler: probability:consistent' --- ### Description A clear and concise description of what the bug is. ### Environment - OS: [e.g. iOS] - Architecture: [e.g. x86, i386] - Go Version: [e.g. 1.15] - `consistent` version: [e.g. v0.14.0, 3c7face] ### Steps To Reproduce 1. Using this code ... 2. Run ... 3. See error ... ### Expected behavior A clear and concise description of what you expected to happen. open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE/bug_report_zpages.md000066400000000000000000000010361470323427300320370ustar00rootroot00000000000000--- name: '[zpages] Bug report' about: Create a report of invalid behavior about the zpages package to help us improve title: '' labels: 'bug, area: zpages, zpages' --- ### Description A clear and concise description of what the bug is. ### Environment - OS: [e.g. iOS] - Architecture: [e.g. x86, i386] - Go Version: [e.g. 1.15] - `zpages` version: [e.g. v0.14.0, 3c7face] ### Steps To Reproduce 1. Using this code ... 2. Run ... 3. See error ... ### Expected behavior A clear and concise description of what you expected to happen. open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE/feature_request.md000066400000000000000000000012231470323427300315170ustar00rootroot00000000000000--- name: 'Feature request' about: Suggest an idea title: '' labels: enhancement labels: 'enhancement' --- ### Problem Statement A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] ### Proposed Solution A clear and concise description of what you want to happen. #### Alternatives A clear and concise description of any alternative solutions or features you've considered. #### Prior Art A clear and concise list of any similar and existing solutions from other projects that provide context to possible solutions. ### Additional Context Add any other context or screenshots about the feature request here. feature_request_autoexporter.md000066400000000000000000000013171470323427300342650ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[autoexporter] Feature request' about: Suggest an idea for the autoexporter package title: '' labels: enhancement labels: 'enhancement, area: exporter' --- ### Problem Statement A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] ### Proposed Solution A clear and concise description of what you want to happen. #### Alternatives A clear and concise description of any alternative solutions or features you've considered. #### Prior Art A clear and concise list of any similar and existing solutions from other projects that provide context to possible solutions. ### Additional Context Add any other context or screenshots about the feature request here. open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE/feature_request_config.md000066400000000000000000000013011470323427300330410ustar00rootroot00000000000000--- name: '[config] Feature request' about: Suggest an idea for the config package title: '' labels: enhancement labels: 'enhancement, area: config' --- ### Problem Statement A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] ### Proposed Solution A clear and concise description of what you want to happen. #### Alternatives A clear and concise description of any alternative solutions or features you've considered. #### Prior Art A clear and concise list of any similar and existing solutions from other projects that provide context to possible solutions. ### Additional Context Add any other context or screenshots about the feature request here. feature_request_detector_aws_ec2.md000066400000000000000000000013361470323427300347410ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[detector: aws:ec2] Feature request' about: Suggest an idea for the ec2 package title: '' labels: enhancement labels: 'enhancement, area: detector, detector: aws:ec2' --- ### Problem Statement A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] ### Proposed Solution A clear and concise description of what you want to happen. #### Alternatives A clear and concise description of any alternative solutions or features you've considered. #### Prior Art A clear and concise list of any similar and existing solutions from other projects that provide context to possible solutions. ### Additional Context Add any other context or screenshots about the feature request here. feature_request_detector_aws_ecs.md000066400000000000000000000013361470323427300350420ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[detector: aws:ecs] Feature request' about: Suggest an idea for the ecs package title: '' labels: enhancement labels: 'enhancement, area: detector, detector: aws:ecs' --- ### Problem Statement A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] ### Proposed Solution A clear and concise description of what you want to happen. #### Alternatives A clear and concise description of any alternative solutions or features you've considered. #### Prior Art A clear and concise list of any similar and existing solutions from other projects that provide context to possible solutions. ### Additional Context Add any other context or screenshots about the feature request here. feature_request_detector_aws_eks.md000066400000000000000000000013361470323427300350520ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[detector: aws:eks] Feature request' about: Suggest an idea for the eks package title: '' labels: enhancement labels: 'enhancement, area: detector, detector: aws:eks' --- ### Problem Statement A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] ### Proposed Solution A clear and concise description of what you want to happen. #### Alternatives A clear and concise description of any alternative solutions or features you've considered. #### Prior Art A clear and concise list of any similar and existing solutions from other projects that provide context to possible solutions. ### Additional Context Add any other context or screenshots about the feature request here. feature_request_detector_aws_lambda.md000066400000000000000000000013471470323427300355120ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[detector: aws:lambda] Feature request' about: Suggest an idea for the lambda package title: '' labels: enhancement labels: 'enhancement, area: detector, detector: aws:lambda' --- ### Problem Statement A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] ### Proposed Solution A clear and concise description of what you want to happen. #### Alternatives A clear and concise description of any alternative solutions or features you've considered. #### Prior Art A clear and concise list of any similar and existing solutions from other projects that provide context to possible solutions. ### Additional Context Add any other context or screenshots about the feature request here. feature_request_detector_gcp.md000066400000000000000000000013361470323427300341670ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[detector: aws:gcp] Feature request' about: Suggest an idea for the gcp package title: '' labels: enhancement labels: 'enhancement, area: detector, detector: aws:gcp' --- ### Problem Statement A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] ### Proposed Solution A clear and concise description of what you want to happen. #### Alternatives A clear and concise description of any alternative solutions or features you've considered. #### Prior Art A clear and concise list of any similar and existing solutions from other projects that provide context to possible solutions. ### Additional Context Add any other context or screenshots about the feature request here. open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE/feature_request_instrgen.md000066400000000000000000000014011470323427300334260ustar00rootroot00000000000000--- name: '[instrgen] Feature request' about: Suggest an idea for the instrgen package title: '' labels: enhancement labels: 'enhancement, area: instrgen' assignees: '@open-telemetry/go-instrumentation-approvers' --- ### Problem Statement A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] ### Proposed Solution A clear and concise description of what you want to happen. #### Alternatives A clear and concise description of any alternative solutions or features you've considered. #### Prior Art A clear and concise list of any similar and existing solutions from other projects that provide context to possible solutions. ### Additional Context Add any other context or screenshots about the feature request here. feature_request_instrumentation_host.md000066400000000000000000000013561470323427300360270ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[instrumentation: host] Feature request' about: Suggest an idea for the host package title: '' labels: enhancement labels: 'enhancement, area: instrumentation, instrumentation: host' --- ### Problem Statement A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] ### Proposed Solution A clear and concise description of what you want to happen. #### Alternatives A clear and concise description of any alternative solutions or features you've considered. #### Prior Art A clear and concise list of any similar and existing solutions from other projects that provide context to possible solutions. ### Additional Context Add any other context or screenshots about the feature request here. feature_request_instrumentation_otelaws.md000066400000000000000000000013671470323427300365320ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[instrumentation: otelaws] Feature request' about: Suggest an idea for the otelaws package title: '' labels: enhancement labels: 'enhancement, area: instrumentation, instrumentation: otelaws' --- ### Problem Statement A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] ### Proposed Solution A clear and concise description of what you want to happen. #### Alternatives A clear and concise description of any alternative solutions or features you've considered. #### Prior Art A clear and concise list of any similar and existing solutions from other projects that provide context to possible solutions. ### Additional Context Add any other context or screenshots about the feature request here. feature_request_instrumentation_otelecho.md000066400000000000000000000013721470323427300366520ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[instrumentation: otelecho] Feature request' about: Suggest an idea for the otelecho package title: '' labels: enhancement labels: 'enhancement, area: instrumentation, instrumentation: otelecho' --- ### Problem Statement A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] ### Proposed Solution A clear and concise description of what you want to happen. #### Alternatives A clear and concise description of any alternative solutions or features you've considered. #### Prior Art A clear and concise list of any similar and existing solutions from other projects that provide context to possible solutions. ### Additional Context Add any other context or screenshots about the feature request here. feature_request_instrumentation_otelgin.md000066400000000000000000000013671470323427300365150ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[instrumentation: otelgin] Feature request' about: Suggest an idea for the otelgin package title: '' labels: enhancement labels: 'enhancement, area: instrumentation, instrumentation: otelgin' --- ### Problem Statement A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] ### Proposed Solution A clear and concise description of what you want to happen. #### Alternatives A clear and concise description of any alternative solutions or features you've considered. #### Prior Art A clear and concise list of any similar and existing solutions from other projects that provide context to possible solutions. ### Additional Context Add any other context or screenshots about the feature request here. feature_request_instrumentation_otelgrpc.md000066400000000000000000000013721470323427300366670ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[instrumentation: otelgrpc] Feature request' about: Suggest an idea for the otelgrpc package title: '' labels: enhancement labels: 'enhancement, area: instrumentation, instrumentation: otelgrpc' --- ### Problem Statement A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] ### Proposed Solution A clear and concise description of what you want to happen. #### Alternatives A clear and concise description of any alternative solutions or features you've considered. #### Prior Art A clear and concise list of any similar and existing solutions from other projects that provide context to possible solutions. ### Additional Context Add any other context or screenshots about the feature request here. feature_request_instrumentation_otelhttp.md000066400000000000000000000013721470323427300367130ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[instrumentation: otelhttp] Feature request' about: Suggest an idea for the otelhttp package title: '' labels: enhancement labels: 'enhancement, area: instrumentation, instrumentation: otelhttp' --- ### Problem Statement A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] ### Proposed Solution A clear and concise description of what you want to happen. #### Alternatives A clear and concise description of any alternative solutions or features you've considered. #### Prior Art A clear and concise list of any similar and existing solutions from other projects that provide context to possible solutions. ### Additional Context Add any other context or screenshots about the feature request here. feature_request_instrumentation_otelhttptrace.md000066400000000000000000000014111470323427300377240ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[instrumentation: otelhttptrace] Feature request' about: Suggest an idea for the otelhttptrace package title: '' labels: enhancement labels: 'enhancement, area: instrumentation, instrumentation: otelhttptrace' --- ### Problem Statement A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] ### Proposed Solution A clear and concise description of what you want to happen. #### Alternatives A clear and concise description of any alternative solutions or features you've considered. #### Prior Art A clear and concise list of any similar and existing solutions from other projects that provide context to possible solutions. ### Additional Context Add any other context or screenshots about the feature request here. feature_request_instrumentation_otellambda.md000066400000000000000000000014001470323427300371440ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[instrumentation: otellambda] Feature request' about: Suggest an idea for the otellambda package title: '' labels: enhancement labels: 'enhancement, area: instrumentation, instrumentation: otellambda' --- ### Problem Statement A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] ### Proposed Solution A clear and concise description of what you want to happen. #### Alternatives A clear and concise description of any alternative solutions or features you've considered. #### Prior Art A clear and concise list of any similar and existing solutions from other projects that provide context to possible solutions. ### Additional Context Add any other context or screenshots about the feature request here. feature_request_instrumentation_otelmacaron.md000066400000000000000000000014031470323427300373470ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[instrumentation: otelmacaron] Feature request' about: Suggest an idea for the otelmacaron package title: '' labels: enhancement labels: 'enhancement, area: instrumentation, instrumentation: otelmacaron' --- ### Problem Statement A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] ### Proposed Solution A clear and concise description of what you want to happen. #### Alternatives A clear and concise description of any alternative solutions or features you've considered. #### Prior Art A clear and concise list of any similar and existing solutions from other projects that provide context to possible solutions. ### Additional Context Add any other context or screenshots about the feature request here. feature_request_instrumentation_otelmongo.md000066400000000000000000000013751470323427300370560ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[instrumentation: otelmongo] Feature request' about: Suggest an idea for the otelmongo package title: '' labels: enhancement labels: 'enhancement, area: instrumentation, instrumentation: otelmongo' --- ### Problem Statement A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] ### Proposed Solution A clear and concise description of what you want to happen. #### Alternatives A clear and concise description of any alternative solutions or features you've considered. #### Prior Art A clear and concise list of any similar and existing solutions from other projects that provide context to possible solutions. ### Additional Context Add any other context or screenshots about the feature request here. feature_request_instrumentation_otelmux.md000066400000000000000000000013671470323427300365510ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[instrumentation: otelmux] Feature request' about: Suggest an idea for the otelmux package title: '' labels: enhancement labels: 'enhancement, area: instrumentation, instrumentation: otelmux' --- ### Problem Statement A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] ### Proposed Solution A clear and concise description of what you want to happen. #### Alternatives A clear and concise description of any alternative solutions or features you've considered. #### Prior Art A clear and concise list of any similar and existing solutions from other projects that provide context to possible solutions. ### Additional Context Add any other context or screenshots about the feature request here. feature_request_instrumentation_otelrestful.md000066400000000000000000000014031470323427300374130ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[instrumentation: otelrestful] Feature request' about: Suggest an idea for the otelrestful package title: '' labels: enhancement labels: 'enhancement, area: instrumentation, instrumentation: otelrestful' --- ### Problem Statement A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] ### Proposed Solution A clear and concise description of what you want to happen. #### Alternatives A clear and concise description of any alternative solutions or features you've considered. #### Prior Art A clear and concise list of any similar and existing solutions from other projects that provide context to possible solutions. ### Additional Context Add any other context or screenshots about the feature request here. feature_request_instrumentation_runtime.md000066400000000000000000000013671470323427300365370ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[instrumentation: runtime] Feature request' about: Suggest an idea for the runtime package title: '' labels: enhancement labels: 'enhancement, area: instrumentation, instrumentation: runtime' --- ### Problem Statement A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] ### Proposed Solution A clear and concise description of what you want to happen. #### Alternatives A clear and concise description of any alternative solutions or features you've considered. #### Prior Art A clear and concise list of any similar and existing solutions from other projects that provide context to possible solutions. ### Additional Context Add any other context or screenshots about the feature request here. feature_request_propagator_autoprop.md000066400000000000000000000013551470323427300356350ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[propagator: autoprop] Feature request' about: Suggest an idea for the autoprop package title: '' labels: enhancement labels: 'enhancement, area: propagators, propagators: autoprop' --- ### Problem Statement A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] ### Proposed Solution A clear and concise description of what you want to happen. #### Alternatives A clear and concise description of any alternative solutions or features you've considered. #### Prior Art A clear and concise list of any similar and existing solutions from other projects that provide context to possible solutions. ### Additional Context Add any other context or screenshots about the feature request here. feature_request_propagator_aws.md000066400000000000000000000013511470323427300345520ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[propagator: aws:xray] Feature request' about: Suggest an idea for the xray package title: '' labels: enhancement labels: 'enhancement, area: propagators, propagators: aws:xray' --- ### Problem Statement A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] ### Proposed Solution A clear and concise description of what you want to happen. #### Alternatives A clear and concise description of any alternative solutions or features you've considered. #### Prior Art A clear and concise list of any similar and existing solutions from other projects that provide context to possible solutions. ### Additional Context Add any other context or screenshots about the feature request here. feature_request_propagator_aws_xray.md000066400000000000000000000013361470323427300356200ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[propagator: aws] Feature request' about: Suggest an idea for the aws package title: '' labels: enhancement labels: 'enhancement, area: propagators, propagators: aws' --- ### Problem Statement A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] ### Proposed Solution A clear and concise description of what you want to happen. #### Alternatives A clear and concise description of any alternative solutions or features you've considered. #### Prior Art A clear and concise list of any similar and existing solutions from other projects that provide context to possible solutions. ### Additional Context Add any other context or screenshots about the feature request here. feature_request_propagator_b3.md000066400000000000000000000013331470323427300342640ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[propagator: b3] Feature request' about: Suggest an idea for the b3 package title: '' labels: enhancement labels: 'enhancement, area: propagators, propagators: b3' --- ### Problem Statement A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] ### Proposed Solution A clear and concise description of what you want to happen. #### Alternatives A clear and concise description of any alternative solutions or features you've considered. #### Prior Art A clear and concise list of any similar and existing solutions from other projects that provide context to possible solutions. ### Additional Context Add any other context or screenshots about the feature request here. feature_request_propagator_jaeger.md000066400000000000000000000013471470323427300352220ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[propagator: jaeger] Feature request' about: Suggest an idea for the jaeger package title: '' labels: enhancement labels: 'enhancement, area: propagators, propagators: jaeger' --- ### Problem Statement A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] ### Proposed Solution A clear and concise description of what you want to happen. #### Alternatives A clear and concise description of any alternative solutions or features you've considered. #### Prior Art A clear and concise list of any similar and existing solutions from other projects that provide context to possible solutions. ### Additional Context Add any other context or screenshots about the feature request here. feature_request_propagator_opencensus.md000066400000000000000000000013631470323427300361450ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[propagator: opencensus] Feature request' about: Suggest an idea for the opencensus package title: '' labels: enhancement labels: 'enhancement, area: propagators, propagators: opencensus' --- ### Problem Statement A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] ### Proposed Solution A clear and concise description of what you want to happen. #### Alternatives A clear and concise description of any alternative solutions or features you've considered. #### Prior Art A clear and concise list of any similar and existing solutions from other projects that provide context to possible solutions. ### Additional Context Add any other context or screenshots about the feature request here. feature_request_propagator_ot.md000066400000000000000000000013331470323427300344020ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[propagator: ot] Feature request' about: Suggest an idea for the ot package title: '' labels: enhancement labels: 'enhancement, area: propagators, propagators: ot' --- ### Problem Statement A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] ### Proposed Solution A clear and concise description of what you want to happen. #### Alternatives A clear and concise description of any alternative solutions or features you've considered. #### Prior Art A clear and concise list of any similar and existing solutions from other projects that provide context to possible solutions. ### Additional Context Add any other context or screenshots about the feature request here. feature_request_sampler_aws_xray.md000066400000000000000000000013171470323427300351040ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[sampler: aws:xray] Feature request' about: Suggest an idea for the xray package title: '' labels: enhancement labels: 'enhancement, sampler: aws:xray' --- ### Problem Statement A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] ### Proposed Solution A clear and concise description of what you want to happen. #### Alternatives A clear and concise description of any alternative solutions or features you've considered. #### Prior Art A clear and concise list of any similar and existing solutions from other projects that provide context to possible solutions. ### Additional Context Add any other context or screenshots about the feature request here. feature_request_sampler_jaegerremote.md000066400000000000000000000013371470323427300357220ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[sampler: jaegerremote] Feature request' about: Suggest an idea for the jaegerremote package title: '' labels: enhancement labels: 'enhancement, sampler: jaegerremote' --- ### Problem Statement A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] ### Proposed Solution A clear and concise description of what you want to happen. #### Alternatives A clear and concise description of any alternative solutions or features you've considered. #### Prior Art A clear and concise list of any similar and existing solutions from other projects that provide context to possible solutions. ### Additional Context Add any other context or screenshots about the feature request here. feature_request_sampler_probability_consistent.md000066400000000000000000000013751470323427300400440ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE--- name: '[sampler: probability:consistent] Feature request' about: Suggest an idea for the probability:consistent package title: '' labels: enhancement labels: 'enhancement, sampler: probability:consistent' --- ### Problem Statement A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] ### Proposed Solution A clear and concise description of what you want to happen. #### Alternatives A clear and concise description of any alternative solutions or features you've considered. #### Prior Art A clear and concise list of any similar and existing solutions from other projects that provide context to possible solutions. ### Additional Context Add any other context or screenshots about the feature request here. open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE/feature_request_zpages.md000066400000000000000000000013111470323427300330660ustar00rootroot00000000000000--- name: '[zpages] Feature request' about: Suggest an idea for the zpages package title: '' labels: enhancement labels: 'enhancement, area: zpages, zpages' --- ### Problem Statement A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] ### Proposed Solution A clear and concise description of what you want to happen. #### Alternatives A clear and concise description of any alternative solutions or features you've considered. #### Prior Art A clear and concise list of any similar and existing solutions from other projects that provide context to possible solutions. ### Additional Context Add any other context or screenshots about the feature request here. open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE/instrumentation-request.md000066400000000000000000000035301470323427300332500ustar00rootroot00000000000000--- name: Instrumentation Request about: Suggest instrumentation to include in this project title: Request to Add Instrumentation for labels: 'enhancement, area: instrumentation' assignees: '' --- ## Background **Package Link**: ### Why can this instrumentation not be included in the package itself? ### Why can this instrumentation not be hosted in a dedicated repository? ## Proposed Solution ### Tracing - attributes: - - events: - - links: - ### Metrics Instruments - : - type: - unit: - description: - attributes: - ### Prior Art - ## Tasks - Code complete: - [ ] Comprehensive unit tests. - [ ] End-to-end integration tests. - [ ] Tests all passing. - [ ] Instrumentation functionality verified. - Documented - [ ] Added to the [OpenTelemetry Registry](https://opentelemetry.io/registry/) - [ ] README included for the module describing high-level purpose. - [ ] Complete documentation of all public API including package documentation. - [ ] [Instrumentation documentation](https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/instrumentation/README.md#instrumentation-packages) updated. - Examples - [ ] `Dockerfile` file to build example application. - [ ] `docker-compose.yml` to run example in a docker environment to demonstrate instrumentation. open-telemetry-opentelemetry-go-contrib-e5abccb/.github/ISSUE_TEMPLATE/owner.md000066400000000000000000000023171470323427300274530ustar00rootroot00000000000000--- name: 'Code Owner Request' about: Request to become a Code Owner for a module title: 'Request to become a Code Owner' --- Module: [e.g. go.opentelemetry.io/contrib/zpages] ### Requirements - [ ] I am a [member of the OpenTelemetry organization] - [ ] I will maintain my OpenTelemetry organization membership as a Code Owner - [ ] I have good working knowledge of the code in the module - [ ] I have good working knowledge of the technology the module supports - [ ] I understand I will be responsible for keeping up with the changes to technology the module supports - [ ] I understand I will be expected to review any Pull Requests or Issues created that relate to this module - [ ] I understand I will be responsible for the stability and versioning compliance of the module - [ ] I understand I will be responsible for deciding any additional Code Owners of the module [member of the OpenTelemetry organization]: https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#member ### Relevant experience List any PRs/Issues you have interacted with in this repository for this module. Additionally, provide any experience you have related to the underlying technology the module supports. open-telemetry-opentelemetry-go-contrib-e5abccb/.github/codecov.yaml000066400000000000000000000004071470323427300261200ustar00rootroot00000000000000codecov: require_ci_to_pass: yes coverage: precision: 1 round: down range: "70...100" status: project: default: target: auto threshold: 1% comment: layout: "reach,diff,flags,tree" behavior: default require_changes: yes open-telemetry-opentelemetry-go-contrib-e5abccb/.github/workflows/000077500000000000000000000000001470323427300256465ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/.github/workflows/changelog.yml000066400000000000000000000025661470323427300303310ustar00rootroot00000000000000# This action requires that any PR targeting the main branch should touch at # least one CHANGELOG file. If a CHANGELOG entry is not required, or if # performing maintenance on the Changelog, add either \"[chore]\" to the title of # the pull request or add the \"Skip Changelog\" label to disable this action. name: changelog on: pull_request: types: [opened, synchronize, reopened, labeled, unlabeled] branches: - main jobs: changelog: runs-on: ubuntu-latest if: ${{ !contains(github.event.pull_request.labels.*.name, 'dependencies') && !contains(github.event.pull_request.labels.*.name, 'Skip Changelog') && !contains(github.event.pull_request.title, '[chore]')}} steps: - uses: actions/checkout@v4 - name: Check for CHANGELOG changes run: | # Only the latest commit of the feature branch is available # automatically. To diff with the base branch, we need to # fetch that too (and we only need its latest commit). git fetch origin ${{ github.base_ref }} --depth=1 if [[ $(git diff --name-only FETCH_HEAD | grep CHANGELOG) ]] then echo "A CHANGELOG was modified. Looks good!" else echo "No CHANGELOG was modified." echo "Please add a CHANGELOG entry, or add the \"Skip Changelog\" label if not required." false fi open-telemetry-opentelemetry-go-contrib-e5abccb/.github/workflows/ci.yml000066400000000000000000000102521470323427300267640ustar00rootroot00000000000000name: build_and_test on: push: branches: - main pull_request: env: # path to where test results will be saved TEST_RESULTS: /tmp/test-results # Default version of Go to use by CI workflows. This should be the latest # release of Go; developers likely use the latest release in development and # we want to catch any bugs (e.g. lint errors, race detection) with this # release before they are merged. The Go compatibility guarantees ensure # backwards compatibility with the previous two minor releases and we # explicitly test our code for these versions so keeping this at prior # versions does not add value. DEFAULT_GO_VERSION: "~1.23.0" jobs: lint: runs-on: ubuntu-latest steps: - name: Checkout Repo uses: actions/checkout@v4 with: fetch-depth: 0 ## Needed for "Set tools/go.mod timestamp" step. - name: Install Go uses: actions/setup-go@v5 with: go-version: ${{ env.DEFAULT_GO_VERSION }} check-latest: true cache-dependency-path: "**/go.sum" - name: Tools cache uses: actions/cache@v4 env: cache-name: go-tools-cache with: path: .tools key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('./tools/**') }} # The step below is needed to not rebuild all the build tools. - name: Set tools/go.mod timestamp run: | filename="tools/go.mod" unixtime=$(git log -1 --format="%at" -- "${filename}") touchtime=$(date -d @$unixtime +'%Y%m%d%H%M.%S') touch -t ${touchtime} "${filename}" ls -la --time-style=full-iso "${filename}" - name: Generate run: make generate - name: Run linters run: make license-check lint vanity-import-check - name: Build run: make build - name: Check clean repository run: make check-clean-work-tree test-race: runs-on: ubuntu-latest steps: - name: Checkout Repo uses: actions/checkout@v4 - name: Install Go uses: actions/setup-go@v5 with: go-version: ${{ env.DEFAULT_GO_VERSION }} check-latest: true cache-dependency-path: "**/go.sum" - name: Run tests with race detector run: make test-race test-coverage: runs-on: ubuntu-latest steps: - name: Checkout Repo uses: actions/checkout@v4 - name: Install Go uses: actions/setup-go@v5 with: go-version: ${{ env.DEFAULT_GO_VERSION }} check-latest: true cache-dependency-path: "**/go.sum" - name: Run coverage tests run: | make test-coverage mkdir $TEST_RESULTS cp coverage.out $TEST_RESULTS cp coverage.txt $TEST_RESULTS cp coverage.html $TEST_RESULTS - name: Upload coverage report uses: codecov/codecov-action@v4.6.0 env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} with: file: ./coverage.txt verbose: true - name: Store coverage test output uses: actions/upload-artifact@v4 with: name: opentelemetry-go-contrib-test-output path: ${{ env.TEST_RESULTS }} compatibility-test: strategy: matrix: go-version: ["1.23.0", "~1.22.4"] platform: - os: ubuntu-latest arch: "386" - os: ubuntu-latest arch: amd64 - os: macos-13 arch: amd64 - os: macos-latest arch: arm64 - os: windows-latest arch: "386" - os: windows-latest arch: amd64 runs-on: ${{ matrix.platform.os }} steps: - name: Checkout code uses: actions/checkout@v4 - name: Install Go uses: actions/setup-go@v5 with: go-version: ${{ matrix.go-version }} check-latest: true cache-dependency-path: "**/go.sum" - name: Run tests env: GOARCH: ${{ matrix.platform.arch }} run: make test-short test-compatibility: runs-on: ubuntu-latest needs: [compatibility-test] if: always() steps: - name: Test if compatibility-test workflow passed run: | echo ${{ needs.compatibility-test.result }} test ${{ needs.compatibility-test.result }} == "success" open-telemetry-opentelemetry-go-contrib-e5abccb/.github/workflows/codeql_analysis.yml000066400000000000000000000021511470323427300315420ustar00rootroot00000000000000on: workflow_dispatch: schedule: # ┌───────────── minute (0 - 59) # │ ┌───────────── hour (0 - 23) # │ │ ┌───────────── day of the month (1 - 31) # │ │ │ ┌───────────── month (1 - 12 or JAN-DEC) # │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT) # │ │ │ │ │ # │ │ │ │ │ # │ │ │ │ │ # * * * * * - cron: '30 1 * * *' push: branches: [ main ] pull_request: jobs: CodeQL-Build: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v3 with: languages: go - name: Autobuild uses: github/codeql-action/autobuild@v3 - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 open-telemetry-opentelemetry-go-contrib-e5abccb/.github/workflows/codespell.yml000066400000000000000000000004321470323427300303420ustar00rootroot00000000000000name: codespell on: push: branches: - main pull_request: jobs: codespell: runs-on: ubuntu-latest steps: - name: Checkout Repo uses: actions/checkout@v4 - name: Codespell run: make codespell - run: make check-clean-work-tree open-telemetry-opentelemetry-go-contrib-e5abccb/.github/workflows/links-fail-fast.yml000066400000000000000000000014151470323427300313560ustar00rootroot00000000000000name: Links (Fail Fast) on: push: pull_request: jobs: check-links: runs-on: ubuntu-latest permissions: contents: read steps: - uses: actions/checkout@v4 - name: Restore lychee cache uses: actions/cache@v4 with: path: .lycheecache key: cache-lychee-${{ github.sha }} restore-keys: cache-lychee- - name: Link Checker uses: lycheeverse/lychee-action@v1.10.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: fail: true # TODO: Remove 429s exception once https://github.com/open-telemetry/opentelemetry-go-contrib/issues/6183 is resolved args: --max-concurrency 5 --cache --max-cache-age 1d --accept 100..=103,200..=299,429 . open-telemetry-opentelemetry-go-contrib-e5abccb/.github/workflows/links.yml000066400000000000000000000021221470323427300275060ustar00rootroot00000000000000name: Links on: repository_dispatch: workflow_dispatch: schedule: # Everyday at 9:00 AM. - cron: "0 9 * * *" jobs: check-links: runs-on: ubuntu-latest permissions: contents: read steps: - name: Checkout Repo uses: actions/checkout@v4 - name: Restore lychee cache uses: actions/cache@v4 with: path: .lycheecache key: cache-lychee-${{ github.sha }} restore-keys: cache-lychee- - name: Link Checker id: lychee uses: lycheeverse/lychee-action@v1.10.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: # TODO: Remove 429s exception once https://github.com/open-telemetry/opentelemetry-go-contrib/issues/6183 is resolved args: --max-concurrency 1 --cache --max-cache-age 1d --accept 100..=103,200..=299,429 . - name: Create Issue From File if: steps.lychee.outputs.exit_code != 0 uses: peter-evans/create-issue-from-file@v5 with: title: Link Checker Report content-filepath: ./lychee/out.md labels: report, bot-generated open-telemetry-opentelemetry-go-contrib-e5abccb/.github/workflows/protect-released-changelog.yml000066400000000000000000000012701470323427300335600ustar00rootroot00000000000000# This action against that any PR targeting the main branch touches released # sections in CHANGELOG file. If change to released CHANGELOG is required, like # doing a release, add the \"Unlock Released Changelog\" label to disable this action. name: Protect released changelog on: pull_request: types: [opened, synchronize, reopened, labeled, unlabeled] jobs: protect-released-changelog: runs-on: ubuntu-latest if: ${{ !contains(github.event.pull_request.labels.*.name, 'Unlock Released Changelog')}} steps: - uses: actions/checkout@v4 - name: Protect the released changelog run: | ./tools/verify_released_changelog.sh ${{ github.base_ref }} open-telemetry-opentelemetry-go-contrib-e5abccb/.gitignore000066400000000000000000000007531470323427300242460ustar00rootroot00000000000000.DS_Store Thumbs.db .tools/ venv/ .idea/ .vscode/ *.iml *.so *.exe coverage.* go.work go.work.sum examples/dice/dice examples/namedtracer/namedtracer examples/otel-collector/otel-collector examples/opencensus/opencensus examples/passthrough/passthrough examples/prometheus/prometheus examples/zipkin/zipkin instrumentation/google.golang.org/grpc/otelgrpc/example/server/server instrumentation/google.golang.org/grpc/otelgrpc/example/client/client instrgen/driver/testdata/**/*.go_pass_* open-telemetry-opentelemetry-go-contrib-e5abccb/.golangci.yml000066400000000000000000000233651470323427300246460ustar00rootroot00000000000000# See https://github.com/golangci/golangci-lint#config-file run: issues-exit-code: 1 #Default tests: true #Default timeout: 2m linters: # Disable everything by default so upgrades to not include new "default # enabled" linters. disable-all: true # Specifically enable linters we want to use. enable: - asasalint - bodyclose - depguard - errcheck - errorlint - godot - gofumpt - goimports - gosec - gosimple - govet - ineffassign - misspell - revive - staticcheck - tenv - testifylint - typecheck - unconvert - unparam - unused issues: # Maximum issues count per one linter. # Set to 0 to disable. # Default: 50 # Setting to unlimited so the linter only is run once to debug all issues. max-issues-per-linter: 0 # Maximum count of issues with the same text. # Set to 0 to disable. # Default: 3 # Setting to unlimited so the linter only is run once to debug all issues. max-same-issues: 0 # Excluding configuration per-path, per-linter, per-text and per-source. exclude-rules: # TODO: Having appropriate comments for exported objects helps development, # even for objects in internal packages. Appropriate comments for all # exported objects should be added and this exclusion removed. - path: '.*internal/.*' text: "exported (method|function|type|const) (.+) should have comment or be unexported" linters: - revive # Yes, they are, but it's okay in a test. - path: _test\.go text: "exported func.*returns unexported type.*which can be annoying to use" linters: - revive # Example test functions should be treated like main. - path: example.*_test\.go text: "calls to (.+) only in main[(][)] or init[(][)] functions" linters: - revive # It's okay to not run gosec in a test. - path: _test\.go linters: - gosec include: # revive exported should have comment or be unexported. - EXC0012 # revive package comment should be of the form ... - EXC0013 linters-settings: depguard: rules: no-sdk-instrumentation: files: - "**/*/{bridges,instrumentation}/**/*.go" - "!**/*/bridges/prometheus/*.go" # prometheus bridge is meant to use the SDK - "!**/*/instrumentation/runtime/*.go" # runtime instrumentation is meant to use the SDK - "!**/*test/*.go" - "!**/*test/**/*.go" - "!**/*example/*.go" - "!**/*example/**/*.go" deny: - pkg: go.opentelemetry.io/otel/sdk desc: "Bridges and instrumentations should not use the SDKs, except in test or example modules" non-tests: files: - "!$test" - "!**/*test/*.go" deny: - pkg: "testing" - pkg: "github.com/stretchr/testify" - pkg: "crypto/md5" - pkg: "crypto/sha1" - pkg: "crypto/**/pkix" godot: exclude: # Exclude sentence fragments for lists. - '^[ ]*[-•]' # Exclude sentences prefixing a list. - ':$' goimports: local-prefixes: go.opentelemetry.io misspell: locale: US ignore-words: - cancelled revive: # Sets the default failure confidence. # This means that linting errors with less than 0.8 confidence will be ignored. # Default: 0.8 confidence: 0.01 rules: # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#blank-imports - name: blank-imports disabled: false # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#bool-literal-in-expr - name: bool-literal-in-expr disabled: false # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#constant-logical-expr - name: constant-logical-expr disabled: false # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#context-as-argument # TODO (#2877) re-enable linter when it is compatible. https://github.com/golangci/golangci-lint/issues/3280 - name: context-as-argument disabled: true arguments: allowTypesBefore: "*testing.T" # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#context-keys-type - name: context-keys-type disabled: false # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#deep-exit - name: deep-exit disabled: false # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#defer - name: defer disabled: false arguments: - ["call-chain", "loop"] # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#dot-imports - name: dot-imports disabled: false # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#duplicated-imports - name: duplicated-imports disabled: false # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#early-return - name: early-return disabled: false # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#empty-block - name: empty-block disabled: false # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#empty-lines - name: empty-lines disabled: false # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#error-naming - name: error-naming disabled: false # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#error-return - name: error-return disabled: false # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#error-strings - name: error-strings disabled: false # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#errorf - name: errorf disabled: false # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#exported - name: exported disabled: false arguments: - "sayRepetitiveInsteadOfStutters" # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#flag-parameter - name: flag-parameter disabled: false # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#identical-branches - name: identical-branches disabled: false # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#if-return - name: if-return disabled: false # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#increment-decrement - name: increment-decrement disabled: false # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#indent-error-flow - name: indent-error-flow disabled: false # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#import-shadowing - name: import-shadowing disabled: false # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#package-comments - name: package-comments disabled: false # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#range - name: range disabled: false # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#range-val-in-closure - name: range-val-in-closure disabled: false # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#range-val-address - name: range-val-address disabled: false # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#redefines-builtin-id - name: redefines-builtin-id disabled: false # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#string-format - name: string-format disabled: false arguments: - - panic - '/^[^\n]*$/' - must not contain line breaks # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#struct-tag - name: struct-tag disabled: false # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#superfluous-else - name: superfluous-else disabled: false # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#time-equal - name: time-equal disabled: false # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#var-naming - name: var-naming disabled: false arguments: - ["ID"] # AllowList - ["Otel", "Aws", "Gcp"] # DenyList # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#var-declaration - name: var-declaration disabled: false # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unconditional-recursion - name: unconditional-recursion disabled: false # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unexported-return - name: unexported-return disabled: false # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unhandled-error - name: unhandled-error disabled: false arguments: - "fmt.Fprint" - "fmt.Fprintf" - "fmt.Fprintln" - "fmt.Print" - "fmt.Printf" - "fmt.Println" # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unnecessary-stmt - name: unnecessary-stmt disabled: false # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#useless-break - name: useless-break disabled: false # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#waitgroup-by-value - name: waitgroup-by-value disabled: false testifylint: enable-all: true disable: - float-compare - require-error open-telemetry-opentelemetry-go-contrib-e5abccb/.lycheeignore000066400000000000000000000000211470323427300247200ustar00rootroot00000000000000http://localhost open-telemetry-opentelemetry-go-contrib-e5abccb/CHANGELOG.md000066400000000000000000002101431470323427300240630ustar00rootroot00000000000000# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] ## [1.31.0/0.56.0/0.25.0/0.11.0/0.6.0/0.4.0/0.3.0] - 2024-10-14 ### Added - The `Severitier` and `SeverityVar` types are added to `go.opentelemetry.io/contrib/processors/minsev` allowing dynamic configuration of the severity used by the `LogProcessor`. (#6116) - Move examples from `go.opentelemetry.io/otel` to this repository under `examples` directory. (#6158) - Support yaml/json struct tags for generated code in `go.opentelemetry.io/contrib/config`. (#5433) - Add support for parsing YAML configuration via `ParseYAML` in `go.opentelemetry.io/contrib/config`. (#5433) - Add support for temporality preference configuration in `go.opentelemetry.io/contrib/config`. (#5860) ### Changed - The function signature of `NewLogProcessor` in `go.opentelemetry.io/contrib/processors/minsev` has changed to accept the added `Severitier` interface instead of a `log.Severity`. (#6116) - Updated `go.opentelemetry.io/contrib/config` to use the [v0.3.0](https://github.com/open-telemetry/opentelemetry-configuration/releases/tag/v0.3.0) release of schema which includes backwards incompatible changes. (#6126) - `NewSDK` in `go.opentelemetry.io/contrib/config` now returns a no-op SDK if `disabled` is set to `true`. (#6185) - The deprecated `go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho` package has found a Code Owner. The package is no longer deprecated. (#6207) ### Fixed - Possible nil dereference panic in `go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace`. (#5965) - `logrus.Level` transformed to appropriate `log.Severity` in `go.opentelemetry.io/contrib/bridges/otellogrus`. (#6191) ### Removed - The `Minimum` field of the `LogProcessor` in `go.opentelemetry.io/contrib/processors/minsev` is removed. Use `NewLogProcessor` to configure this setting. (#6116) - The deprecated `go.opentelemetry.io/contrib/instrumentation/gopkg.in/macaron.v1/otelmacaron` package is removed. (#6186) - The deprecated `go.opentelemetry.io/contrib/samplers/aws/xray` package is removed. (#6187) ## [1.30.0/0.55.0/0.24.0/0.10.0/0.5.0/0.3.0/0.2.0] - 2024-09-10 ### Added - Add `NewProducer` to `go.opentelemetry.io/contrib/instrumentation/runtime`, which allows collecting the `go.schedule.duration` histogram metric from the Go runtime. (#5991) - Add gRPC protocol support for OTLP log exporter in `go.opentelemetry.io/contrib/exporters/autoexport`. (#6083) ### Removed - Drop support for [Go 1.21]. (#6046, #6047) ### Fixed - Superfluous call to `WriteHeader` when flushing after setting a status code in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`. (#6074) - Superfluous call to `WriteHeader` when writing the response body after setting a status code in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`. (#6055) ## [1.29.0/0.54.0/0.23.0/0.9.0/0.4.0/0.2.0/0.1.0] - 2024-08-23 This release is the last to support [Go 1.21]. The next release will require at least [Go 1.22]. ### Added - Add the `WithSpanAttributes` and `WithMetricAttributes` methods to set custom attributes to the stats handler in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc`. (#5133) - The `go.opentelemetry.io/contrib/bridges/otelzap` module. This module provides an OpenTelemetry logging bridge for `go.uber.org/zap`. (#5191) - Support for the `OTEL_HTTP_CLIENT_COMPATIBILITY_MODE=http/dup` environment variable in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` to emit attributes for both the v1.20.0 and v1.26.0 semantic conventions. (#5401) - The `go.opentelemetry.io/contrib/bridges/otelzerolog` module. This module provides an OpenTelemetry logging bridge for `github.com/rs/zerolog`. (#5405) - Add `WithGinFilter` filter parameter in `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin` to allow filtering requests with `*gin.Context`. (#5743) - Support for stdoutlog exporter in `go.opentelemetry.io/contrib/config`. (#5850) - Add macOS ARM64 platform to the compatibility testing suite. (#5868) - Add new runtime metrics to `go.opentelemetry.io/contrib/instrumentation/runtime`, which are still disabled by default. (#5870) - Add the `WithMetricsAttributesFn` option to allow setting dynamic, per-request metric attributes in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`. (#5876) - The `go.opentelemetry.io/contrib/config` package supports configuring `with_resource_constant_labels` for the prometheus exporter. (#5890) - Support [Go 1.23]. (#6017) ### Removed - The deprecated `go.opentelemetry.io/contrib/processors/baggagecopy` package is removed. (#5853) ### Fixed - Race condition when reading the HTTP body and writing the response in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`. (#5916) ## [1.28.0/0.53.0/0.22.0/0.8.0/0.3.0/0.1.0] - 2024-07-02 ### Added - Add the new `go.opentelemetry.io/contrib/detectors/azure/azurevm` package to provide a resource detector for Azure VMs. (#5422) - Add support to configure views when creating MeterProvider using the config package. (#5654) - The `go.opentelemetry.io/contrib/config` add support to configure periodic reader interval and timeout. (#5661) - Add log support for the autoexport package. (#5733) - Add support for disabling the old runtime metrics using the `OTEL_GO_X_DEPRECATED_RUNTIME_METRICS=false` environment variable. (#5747) - Add support for signal-specific protocols environment variables (`OTEL_EXPORTER_OTLP_TRACES_PROTOCOL`, `OTEL_EXPORTER_OTLP_LOGS_PROTOCOL`, `OTEL_EXPORTER_OTLP_METRICS_PROTOCOL`) in `go.opentelemetry.io/contrib/exporters/autoexport`. (#5816) - The `go.opentelemetry.io/contrib/processors/minsev` module is added. This module provides and experimental logging processor with a configurable threshold for the minimum severity records must have to be recorded. (#5817) - The `go.opentelemetry.io/contrib/processors/baggagecopy` module. This module is a replacement of `go.opentelemetry.io/contrib/processors/baggage/baggagetrace`. (#5824) ### Changed - Improve performance of `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc` with the usage of `WithAttributeSet()` instead of `WithAttribute()`. (#5664) - Improve performance of `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` with the usage of `WithAttributeSet()` instead of `WithAttribute()`. (#5664) - Update `go.opentelemetry.io/contrib/config` to latest released configuration schema which introduces breaking changes where `Attributes` is now a `map[string]interface{}`. (#5758) - Upgrade all dependencies of `go.opentelemetry.io/otel/semconv/v1.25.0` to `go.opentelemetry.io/otel/semconv/v1.26.0`. (#5847) ### Fixed - Custom attributes targeting metrics recorded by the `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` are not ignored anymore. (#5129) - The double setup in `go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace/example` that caused duplicate traces. (#5564) - The superfluous `response.WriteHeader` call in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` when the response writer is flushed. (#5634) - Use `c.FullPath()` method to set `http.route` attribute in `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin`. (#5734) - Out-of-bounds panic in case of invalid span ID in `go.opentelemetry.io/contrib/propagators/b3`. (#5754) ### Deprecated - The `go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho` package is deprecated. If you would like to become a Code Owner of this module and prevent it from being removed, see [#5550]. (#5645) - The `go.opentelemetry.io/contrib/instrumentation/gopkg.in/macaron.v1/otelmacaron` package is deprecated. If you would like to become a Code Owner of this module and prevent it from being removed, see [#5552]. (#5646) - The `go.opentelemetry.io/contrib/samplers/aws/xray` package is deprecated. If you would like to become a Code Owner of this module and prevent it from being removed, see [#5554]. (#5647) - The `go.opentelemetry.io/contrib/processors/baggage/baggagetrace` package is deprecated. Use the added `go.opentelemetry.io/contrib/processors/baggagecopy` package instead. (#5824) - Use `baggagecopy.NewSpanProcessor` as a replacement for `baggagetrace.New`. - `NewSpanProcessor` accepts a `Filter` function type that selects which baggage members are added to a span. - `NewSpanProcessor` returns a `*baggagecopy.SpanProcessor` instead of a `trace.SpanProcessor` interface. The returned type still implements the interface. [#5550]: https://github.com/open-telemetry/opentelemetry-go-contrib/issues/5550 [#5552]: https://github.com/open-telemetry/opentelemetry-go-contrib/issues/5552 [#5554]: https://github.com/open-telemetry/opentelemetry-go-contrib/issues/5554 ## [1.27.0/0.52.0/0.21.0/0.7.0/0.2.0] - 2024-05-21 ### Added - Add an experimental `OTEL_METRICS_PRODUCERS` environment variable to `go.opentelemetry.io/contrib/autoexport` to be set metrics producers. (#5281) - `prometheus` and `none` are supported values. You can specify multiple producers separated by a comma. - Add `WithFallbackMetricProducer` option that adds a fallback if the `OTEL_METRICS_PRODUCERS` is not set or empty. - The `go.opentelemetry.io/contrib/processors/baggage/baggagetrace` module. This module provides a Baggage Span Processor. (#5404) - Add gRPC trace `Filter` for stats handler to `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc`. (#5196) - Add a repository Code Ownership Policy. (#5555) - The `go.opentelemetry.io/contrib/bridges/otellogrus` module. This module provides an OpenTelemetry logging bridge for `github.com/sirupsen/logrus`. (#5355) - The `WithVersion` option function in `go.opentelemetry.io/contrib/bridges/otelslog`. This option function is used as a replacement of `WithInstrumentationScope` to specify the logged package version. (#5588) - The `WithSchemaURL` option function in `go.opentelemetry.io/contrib/bridges/otelslog`. This option function is used as a replacement of `WithInstrumentationScope` to specify the semantic convention schema URL for the logged records. (#5588) - Add support for Cloud Run jobs in `go.opentelemetry.io/contrib/detectors/gcp`. (#5559) ### Changed - The gRPC trace `Filter` for interceptor is renamed to `InterceptorFilter`. (#5196) - The gRPC trace filter functions `Any`, `All`, `None`, `Not`, `MethodName`, `MethodPrefix`, `FullMethodName`, `ServiceName`, `ServicePrefix` and `HealthCheck` for interceptor are moved to `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/filters/interceptor`. With this change, the filters in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc` are now working for stats handler. (#5196) - `NewSDK` in `go.opentelemetry.io/contrib/config` now returns a configured SDK with a valid `LoggerProvider`. (#5427) - `NewLogger` now accepts a `name` `string` as the first argument. This parameter is used as a replacement of `WithInstrumentationScope` to specify the name of the logger backing the underlying `Handler`. (#5588) - `NewHandler` now accepts a `name` `string` as the first argument. This parameter is used as a replacement of `WithInstrumentationScope` to specify the name of the logger backing the returned `Handler`. (#5588) - Upgrade all dependencies of `go.opentelemetry.io/otel/semconv/v1.24.0` to `go.opentelemetry.io/otel/semconv/v1.25.0`. (#5605) ### Removed - The `WithInstrumentationScope` option function in `go.opentelemetry.io/contrib/bridges/otelslog` is removed. Use the `name` parameter added to `NewHandler` and `NewLogger` as well as `WithVersion` and `WithSchema` as replacements. (#5588) ### Deprecated - The `InterceptorFilter` type in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc` is deprecated. (#5196) ## [1.26.0/0.51.0/0.20.0/0.6.0/0.1.0] - 2024-04-24 ### Added - `NewSDK` in `go.opentelemetry.io/contrib/config` now returns a configured SDK with a valid `MeterProvider`. (#4804) ### Changed - Change the scope name for the prometheus bridge to `go.opentelemetry.io/contrib/bridges/prometheus` to match the package. (#5396) - Add support for settings additional properties for resource configuration in `go.opentelemetry.io/contrib/config`. (#4832) ### Fixed - Fix bug where an empty exemplar was added to counters in `go.opentelemetry.io/contrib/bridges/prometheus`. (#5395) - Fix bug where the last histogram bucket was missing in `go.opentelemetry.io/contrib/bridges/prometheus`. (#5395) ## [1.25.0/0.50.0/0.19.0/0.5.0/0.0.1] - 2024-04-05 ### Added - Implemented setting the `cloud.resource_id` resource attribute in `go.opentelemetry.io/detectors/aws/ecs` based on the ECS Metadata v4 endpoint. (#5091) - The `go.opentelemetry.io/contrib/bridges/otelslog` module. This module provides an OpenTelemetry logging bridge for "log/slog". (#5335) ### Fixed - Update all dependencies to address [GO-2024-2687]. (#5359) ### Removed - Drop support for [Go 1.20]. (#5163) ## [1.24.0/0.49.0/0.18.0/0.4.0] - 2024-02-23 This release is the last to support [Go 1.20]. The next release will require at least [Go 1.21]. ### Added - Support [Go 1.22]. (#5082) - Add support for Summary metrics to `go.opentelemetry.io/contrib/bridges/prometheus`. (#5089) - Add support for Exponential (native) Histograms in `go.opentelemetry.io/contrib/bridges/prometheus`. (#5093) ### Removed - The deprecated `RequestCount` constant in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` is removed. (#4894) - The deprecated `RequestContentLength` constant in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` is removed. (#4894) - The deprecated `ResponseContentLength` constant in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` is removed. (#4894) - The deprecated `ServerLatency` constant in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` is removed. (#4894) ### Fixed - Retrieving the body bytes count in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` does not cause a data race anymore. (#5080) ## [1.23.0/0.48.0/0.17.0/0.3.0] - 2024-02-06 ### Added - Add client metric support to `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`. (#4707) - Add peer attributes to spans recorded by `NewClientHandler`, `NewServerHandler` in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc`. (#4873) - Add support for `cloud.account.id`, `cloud.availability_zone` and `cloud.region` in the AWS ECS detector. (#4860) ### Changed - The fallback options in `go.opentelemetry.io/contrib/exporters/autoexport` now accept factory functions. (#4891) - `WithFallbackMetricReader(metric.Reader) MetricOption` is replaced with `func WithFallbackMetricReader(func(context.Context) (metric.Reader, error)) MetricOption`. - `WithFallbackSpanExporter(trace.SpanExporter) SpanOption` is replaced with `WithFallbackSpanExporter(func(context.Context) (trace.SpanExporter, error)) SpanOption`. - The `http.server.request_content_length` metric in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` is changed to `http.server.request.size`.(#4707) - The `http.server.response_content_length` metric in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` is changed to `http.server.response.size`.(#4707) ### Deprecated - The `RequestCount`, `RequestContentLength`, `ResponseContentLength`, `ServerLatency` constants in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` are deprecated. (#4707) ### Fixed - Do not panic in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc` if `MeterProvider` returns a `nil` instrument. (#4875) ## [1.22.0/0.47.0/0.16.0/0.2.0] - 2024-01-18 ### Added - Add `SDK.Shutdown` method in `"go.opentelemetry.io/contrib/config"`. (#4583) - `NewSDK` in `go.opentelemetry.io/contrib/config` now returns a configured SDK with a valid `TracerProvider`. (#4741) ### Changed - The semantic conventions used by `go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful` are upgraded to v1.20.0. (#4320) - The semantic conventions used by `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin` are upgraded to v1.20.0. (#4320) - The semantic conventions used by `go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux` are upgraded to v1.20.0. (#4320) - The semantic conventions used by `go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho` are upgraded to v1.20.0. (#4320) - The semantic conventions used by `go.opentelemetry.io/contrib/instrumentation/gopkg.in/macaron.v1/otelmacaron` are upgraded to v1.20.0. (#4320) - The semantic conventions used by `go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace` are upgraded to v1.20.0. (#4320) - The semantic conventions used by `go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace/example` are upgraded to v1.20.0. (#4320) - The semantic conventions used by `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/example` are upgraded to v1.20.0. (#4320) - The semantic conventions used by `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`are upgraded to v1.20.0. (#4320) - Updated configuration schema to include `schema_url` for resource definition and `without_type_suffix` and `without_units` for the Prometheus exporter. (#4727) - The semantic conventions used by the `go.opentelemetry.io/contrib/detectors/aws/ecs` resource detector are upgraded to v1.24.0. (#4803) - The semantic conventions used by the `go.opentelemetry.io/contrib/detectors/aws/lambda` resource detector are upgraded to v1.24.0. (#4803) - The semantic conventions used by the `go.opentelemetry.io/contrib/detectors/aws/ec2` resource detector are upgraded to v1.24.0. (#4803) - The semantic conventions used by the `go.opentelemetry.io/contrib/detectors/aws/eks` resource detector are upgraded to v1.24.0. (#4803) - The semantic conventions used by the `go.opentelemetry.io/contrib/detectors/gcp` resource detector are upgraded to v1.24.0. (#4803) - The semantic conventions used in `go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda/test` are upgraded to v1.24.0. (#4803) ### Fixed - Fix `NewServerHandler` in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc` to correctly set the span status depending on the gRPC status. (#4587) - The `stats.Handler` from `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc` now does not crash when receiving an unexpected context. (#4825) - Update `go.opentelemetry.io/contrib/detectors/aws/ecs` to fix the task ARN when it is not valid. (#3583) - Do not panic in `go.opentelemetry.io/contrib/detectors/aws/ecs` when the container ARN is not valid. (#3583) ## [1.21.1/0.46.1/0.15.1/0.1.1] - 2023-11-16 ### Changed - Upgrade dependencies of OpenTelemetry Go to use the new [`v1.21.0`/`v0.44.0` release](https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.21.0). (#4582) ### Fixed - Fix `StreamClientInterceptor` in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc` to end the spans synchronously. (#4537) - Fix data race in stats handlers when processing messages received and sent metrics in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc`. (#4577) - The stats handlers `NewClientHandler`, `NewServerHandler` in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc` now record RPC durations in `ms` instead of `ns`. (#4548) ## [1.21.0/0.46.0/0.15.0/0.1.0] - 2023-11-10 ### Added - Add `"go.opentelemetry.io/contrib/samplers/jaegerremote".WithSamplingStrategyFetcher` which sets custom fetcher implementation. (#4045) - Add `"go.opentelemetry.io/contrib/config"` package that includes configuration models generated via go-jsonschema. (#4376) - Add `NewSDK` function to `"go.opentelemetry.io/contrib/config"`. The initial implementation only returns noop providers. (#4414) - Add metrics support (No-op, OTLP and Prometheus) to `go.opentelemetry.io/contrib/exporters/autoexport`. (#4229, #4479) - Add support for `console` span exporter and metrics exporter in `go.opentelemetry.io/contrib/exporters/autoexport`. (#4486) - Set unit and description on all instruments in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`. (#4500) - Add metric support for `grpc.StatsHandler` in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc`. (#4356) - Expose the name of the scopes in all instrumentation libraries as `ScopeName`. (#4448) ### Changed - Dropped compatibility testing for [Go 1.19]. The project no longer guarantees support for this version of Go. (#4352) - Upgrade dependencies of OpenTelemetry Go to use the new [`v1.20.0`/`v0.43.0` release](https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.20.0). (#4546) - In `go.opentelemetry.io/contrib/exporters/autoexport`, `Option` was renamed to `SpanOption`. The old name is deprecated but continues to be supported as an alias. (#4229) ### Deprecated - The interceptors (`UnaryClientInterceptor`, `StreamClientInterceptor`, `UnaryServerInterceptor`, `StreamServerInterceptor`, `WithInterceptorFilter`) are deprecated. Use stats handlers (`NewClientHandler`, `NewServerHandler`) instead. (#4534) ### Fixed - The `go.opentelemetry.io/contrib/samplers/jaegerremote` sampler does not panic when the default HTTP round-tripper (`http.DefaultTransport`) is not `*http.Transport`. (#4045) - The `UnaryServerInterceptor` in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc` now sets gRPC status code correctly for the `rpc.server.duration` metric. (#4481) - The `NewClientHandler`, `NewServerHandler` in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc` now honor `otelgrpc.WithMessageEvents` options. (#4536) - The `net.sock.peer.*` and `net.peer.*` high cardinality attributes are removed from the metrics generated by `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc`. (#4322) ## [1.20.0/0.45.0/0.14.0] - 2023-09-28 ### Added - Set the description for the `rpc.server.duration` metric in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc`. (#4302) - Add `NewServerHandler` and `NewClientHandler` that return a `grpc.StatsHandler` used for gRPC instrumentation in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc`. (#3002) - Add new Prometheus bridge module in `go.opentelemetry.io/contrib/bridges/prometheus`. (#4227) ### Changed - Upgrade dependencies of OpenTelemetry Go to use the new [`v1.19.0`/`v0.42.0`/`v0.0.7` release](https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.19.0). - Use `grpc.StatsHandler` for gRPC instrumentation in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/example`. (#4325) ## [1.19.0/0.44.0/0.13.0] - 2023-09-12 ### Added - Add `gcp.gce.instance.name` and `gcp.gce.instance.hostname` resource attributes to `go.opentelemetry.io/contrib/detectors/gcp`. (#4263) ### Changed - The semantic conventions used by `go.opentelemetry.io/contrib/detectors/aws/ec2` have been upgraded to v1.21.0. (#4265) - The semantic conventions used by `go.opentelemetry.io/contrib/detectors/aws/ecs` have been upgraded to v1.21.0. (#4265) - The semantic conventions used by `go.opentelemetry.io/contrib/detectors/aws/eks` have been upgraded to v1.21.0. (#4265) - The semantic conventions used by `go.opentelemetry.io/contrib/detectors/aws/lambda` have been upgraded to v1.21.0. (#4265) - The semantic conventions used by `go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda` have been upgraded to v1.21.0. (#4265) - The `faas.execution` attribute is now `faas.invocation_id`. - The `faas.id` attribute is now `aws.lambda.invoked_arn`. - The semantic conventions used by `go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws` have been upgraded to v1.21.0. (#4265) - The `http.request.method` attribute will only allow known HTTP methods from the metrics generated by `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`. (#4277) ### Removed - The high cardinality attributes `net.sock.peer.addr`, `net.sock.peer.port`, `http.user_agent`, `enduser.id`, and `http.client_ip` were removed from the metrics generated by `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`. (#4277) - The deprecated `go.opentelemetry.io/contrib/instrumentation/github.com/astaxie/beego/otelbeego` module is removed. (#4295) - The deprecated `go.opentelemetry.io/contrib/instrumentation/github.com/go-kit/kit/otelkit` module is removed. (#4295) - The deprecated `go.opentelemetry.io/contrib/instrumentation/github.com/Shopify/sarama/otelsarama` module is removed. (#4295) - The deprecated `go.opentelemetry.io/contrib/instrumentation/github.com/bradfitz/gomemcache/memcache/otelmemcache` module is removed. (#4295) - The deprecated `go.opentelemetry.io/contrib/instrumentation/github.com/gocql/gocql/otelgocql` module is removed. (#4295) ## [1.18.0/0.43.0/0.12.0] - 2023-08-28 ### Added - Add `NewMiddleware` function in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`. (#2964) - The `go.opentelemetry.io/contrib/exporters/autoexport` package to provide configuration of trace exporters with useful defaults and environment variable support. (#2753, #4100, #4130, #4132, #4134) - `WithRouteTag` in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` adds HTTP route attribute to metrics. (#615) - Add `WithSpanOptions` option in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc`. (#3768) - Add testing support for Go 1.21. (#4233) - Add `WithFilter` option to `go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux`. (#4230) ### Changed - Change interceptors in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc` to disable `SENT`/`RECEIVED` events. Use `WithMessageEvents()` to turn back on. (#3964) ### Changed - `go.opentelemetry.io/contrib/detectors/gcp`: Detect `faas.instance` instead of `faas.id`, since `faas.id` is being removed. (#4198) ### Fixed - AWS XRay Remote Sampling to cap `quotaBalance` to 1x quota in `go.opentelemetry.io/contrib/samplers/aws/xray`. (#3651, #3652) - Do not panic when the HTTP request has the "Expect: 100-continue" header in `go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace`. (#3892) - Fix span status value set for non-standard HTTP status codes in modules listed below. (#3966) - `go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful` - `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin` - `go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux` - `go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho` - `go.opentelemetry.io/contrib/instrumentation/gopkg.in/macaron.v1/otelmacaron` - `go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace` - `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` - Do not modify the origin request in `RoundTripper` in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`. (#4033) - Handle empty value of `OTEL_PROPAGATORS` environment variable the same way as when the variable is unset in `go.opentelemetry.io/contrib/propagators/autoprop`. (#4101) - Fix gRPC service/method URL path parsing discrepancies in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc`. (#4135) ### Deprecated - The `go.opentelemetry.io/contrib/instrumentation/github.com/astaxie/beego/otelbeego` module is deprecated. (#4092, #4104) - The `go.opentelemetry.io/contrib/instrumentation/github.com/go-kit/kit/otelkit` module is deprecated. (#4093, #4104) - The `go.opentelemetry.io/contrib/instrumentation/github.com/Shopify/sarama/otelsarama` module is deprecated. (#4099) - The `go.opentelemetry.io/contrib/instrumentation/github.com/bradfitz/gomemcache/memcache/otelmemcache` module is deprecated. (#4164) - The `go.opentelemetry.io/contrib/instrumentation/github.com/gocql/gocql/otelgocql` module is deprecated. (#4164) ### Removed - Remove `Handler` type in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`. (#2964) ## [1.17.0/0.42.0/0.11.0] - 2023-05-23 ### Changed - Use `strings.Cut()` instead of `string.SplitN()` for better readability and memory use. (#3822) ## [1.17.0-rc.1/0.42.0-rc.1/0.11.0-rc.1] - 2023-05-17 ### Changed - Upgrade dependencies of OpenTelemetry Go to use the new [`v1.16.0-rc.1`/`v0.39.0-rc.1` release](https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.16.0-rc.1). - Remove `semver:` prefix from instrumentation version. (#3681, #3798) ### Deprecated - `SemVersion` functions in instrumentation packages are deprecated, use `Version` instead. (#3681, #3798) ## [1.16.1/0.41.1/0.10.1] - 2023-05-02 ### Added - The `WithPublicEndpoint` and `WithPublicEndpointFn` options in `go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux`. (#3661) ### Changed - Upgrade dependencies of OpenTelemetry Go to use the new [`v1.15.1`/`v0.38.1` release](https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.15.1) ### Fixed - AWS XRay Remote Sampling to preserve previous rule if updated rule property has not changed in `go.opentelemetry.io/contrib/samplers/aws/xray`. (#3619, #3620) ## [1.16.0/0.41.0/0.10.0] - 2023-04-28 ### Added - AWS SDK add `rpc.system` attribute in `go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws`. (#3582, #3617) ### Changed - Update `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc` to align gRPC server span status with the changes in the OpenTelemetry specification. (#3685) - Adding the `db.statement` tag to spans in `go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo` is now disabled by default. (#3519) ### Fixed - The error received by `otelecho` middleware is then passed back to upstream middleware instead of being swallowed. (#3656) - Prevent taking from reservoir in AWS XRay Remote Sampler when there is zero capacity in `go.opentelemetry.io/contrib/samplers/aws/xray`. (#3684) - Fix `otelhttp.Handler` in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` to propagate multiple `WriteHeader` calls while persisting the initial `statusCode`. (#3580) ## [1.16.0-rc.2/0.41.0-rc.2/0.10.0-rc.2] - 2023-03-23 ### Added - The `WithPublicEndpoint` and `WithPublicEndpointFn` options in `go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful`. (#3563) ### Fixed - AWS SDK rename attributes `aws.operation`, `aws.service` to `rpc.method`,`rpc.service` in `go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws`. (#3582, #3617) - AWS SDK span name to be of the format `Service.Operation` in `go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws`. (#3582, #3521) - Prevent sampler configuration reset from erroneously sampling first span in `go.opentelemetry.io/contrib/samplers/jaegerremote`. (#3603, #3604) ## [1.16.0-rc.1/0.41.0-rc.1/0.10.0-rc.1] - 2023-03-02 ### Changed - Dropped compatibility testing for [Go 1.18]. The project no longer guarantees support for this version of Go. (#3516) ## [1.15.0/0.40.0/0.9.0] - 2023-02-27 This release is the last to support [Go 1.18]. The next release will require at least [Go 1.19]. ### Added - Support [Go 1.20]. (#3372) - Add `SpanNameFormatter` option to package `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin`. (#3343) ### Changed - Change to use protobuf parser instead of encoding/json to accept enums as strings in `go.opentelemetry.io/contrib/samplers/jaegerremote`. (#3183) ### Fixed - Remove use of deprecated `"math/rand".Seed` in `go.opentelemetry.io/contrib/instrumentation/github.com/Shopify/sarama/otelsarama/example/producer`. (#3396) - Do not assume "aws" partition in ecs detector to prevent panic in `go.opentelemetry.io/contrib/detectors/aws/ecs`. (#3167) - The span name of producer spans from `go.opentelemetry.io/contrib/instrumentation/github.com/Shopify/sarama/otelsarama` is corrected to use `publish` instead of `send`. (#3369) - Attribute types are corrected in `go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws`. (#3369) - `aws.dynamodb.table_names` is now a string slice value. - `aws.dynamodb.global_secondary_indexes` is now a string slice value. - `aws.dynamodb.local_secondary_indexes` is now a string slice value. - `aws.dynamodb.attribute_definitions` is now a string slice value. - `aws.dynamodb.global_secondary_index_updates` is now a string slice value. - `aws.dynamodb.provisioned_read_capacity` is now a `float64` value. - `aws.dynamodb.provisioned_write_capacity` is now a `float64` value. ## [1.14.0/0.39.0/0.8.0] - 2023-02-07 ### Changed - Change `runtime.uptime` instrument in `go.opentelemetry.io/contrib/instrumentation/runtime` from `Int64ObservableUpDownCounter` to `Int64ObservableCounter`, since the value is monotonic. (#3347) - `samplers/jaegerremote`: change to use protobuf parser instead of encoding/json to accept enums as strings. (#3183) ### Fixed - The GCE detector in `go.opentelemetry.io/contrib/detectors/gcp` includes the "cloud.region" attribute when appropriate. (#3367) ## [1.13.0/0.38.0/0.7.0] - 2023-01-30 ### Added - Add `WithSpanNameFormatter` to `go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux` to allow customizing span names. (#3041) - Add missing recommended AWS Lambda resource attributes `faas.instance` and `faas.max_memory` in `go.opentelemetry.io/contrib/detectors/aws/lambda`. (#3148) - Improve documentation for `go.opentelemetry.io/contrib/samplers/jaegerremote` by providing examples of sampling endpoints. (#3147) - Add `WithServerName` to `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` to set the primary server name of a `Handler`. (#3182) ### Changed - Remove expensive calculation of uncompressed message size attribute in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc`. (#3168) - Upgrade all `semconv` packages to use `v1.17.0`. (#3182) - Upgrade dependencies of OpenTelemetry Go to use the new [`v1.12.0`/`v0.35.0` release](https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.12.0). (#3190, #3170) ## [1.12.0/0.37.0/0.6.0] ### Added - Implemented retrieving the [`aws.ecs.*` resource attributes](https://opentelemetry.io/docs/reference/specification/resource/semantic_conventions/cloud_provider/aws/ecs/) in `go.opentelemetry.io/detectors/aws/ecs` based on the ECS Metadata v4 endpoint. (#2626) - The `WithLogger` option to `go.opentelemetry.io/contrib/samplers/jaegerremote` to allow users to pass a `logr.Logger` and have operations logged. (#2566) - Add the `messaging.url` & `messaging.system` attributes to all appropriate SQS operations in the `go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws` package. (#2879) - Add example use of the metrics signal to `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/example`. (#2610) - [otelgin] Add support for filters to the `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin` package to provide the way to control which inbound requests are traced. (#2965, #2963) ### Fixed - Set the status_code span attribute even if the HTTP handler hasn't written anything. (#2822) - Do not wrap http.NoBody in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`, which fixes handling of that special request body. (#2983) ## [1.11.1/0.36.4/0.5.2] ### Added - Add trace context propagation support to `instrumentation/github.com/aws/aws-sdk-go-v2/otelaws` (#2856). - [otelgrpc] Add `WithMeterProvider` function to enable metric and add metric `rpc.server.duration` to otelgrpc instrumentation library. (#2700) ### Changed - Upgrade dependencies of OpenTelemetry Go to use the new [`v1.11.1`/`v0.33.0` release](https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.11.1) ## [1.11.0/0.36.3/0.5.1] ### Changed - Upgrade dependencies of the OpenTelemetry Go Metric SDK to use the new [`v1.11.0`/`v0.32.3` release](https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.11.0) ## [0.36.2] ### Changed - Upgrade dependencies of the OpenTelemetry Go Metric SDK to use the new [`v0.32.2` release](https://github.com/open-telemetry/opentelemetry-go/releases/tag/sdk%2Fmetric%2Fv0.32.2) - Avoid getting a new Tracer for every RPC in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc`. (#2835) - Conditionally compute message size for tracing events using proto v2 API rather than legacy v1 API in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc`. (#2647) ### Deprecated - The `Inject` function in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc` is deprecated. (#2838) - The `Extract` function in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc` is deprecated. (#2838) ## [0.36.1] ### Changed - Upgrade dependencies of the OpenTelemetry Go Metric SDK to use the new [`v0.32.1` release](https://github.com/open-telemetry/opentelemetry-go/releases/tag/sdk%2Fmetric%2Fv0.32.1) ### Removed - Drop support for Go 1.17. The project currently only supports Go 1.18 and above. (#2785) ## [0.36.0] ### Changed - Upgrade dependencies of the OpenTelemetry Go Metric SDK to use the new [`v0.32.0` release](https://github.com/open-telemetry/opentelemetry-go/releases/tag/sdk%2Fmetric%2Fv0.32.0). (#2781, #2756, #2758, #2760, #2762) ## [1.10.0/0.35.0/0.5.0] ### Changed - Rename the `Typ` field of `"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc".InterceptorInfo` to `Type`. (#2688) - Use Go 1.19 as the default version for CI testing/linting. (#2675) ### Fixed - Fix the Jaeger propagator rejecting trace IDs that are both shorter than 128 bits and not exactly 64 bits long (while not being 0). Also fix the propagator rejecting span IDs shorter than 64 bits. This fixes compatibility with Jaeger clients encoding trace and span IDs as variable-length hex strings, [as required by the Jaeger propagation format](https://www.jaegertracing.io/docs/1.37/client-libraries/#value). (#2731) ## [1.9.0/0.34.0/0.4.0] - 2022-08-02 ### Added - Add gRPC trace `Filter` to the `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc` package to provide the way to filter the traces automatically generated in interceptors. (#2572) - The `TextMapPropagator` function to `go.opentelemetry.io/contrib/propagators/autoprop`. This function is used to return a composite `TextMapPropagator` from registered names (instead of having to specify with an environment variable). (#2593) ### Changed - Upgraded all `semconv` package use to `v1.12.0`. (#2589) ## [1.8.0/0.33.0] - 2022-07-08 ### Added - The `go.opentelemetry.io/contrib/propagators/autoprop` package to provide configuration of propagators with useful defaults and envar support. (#2258) - `WithPublicEndpointFn` hook to dynamically detect public HTTP requests and set their trace parent as a link. (#2342) ### Fixed - Fix the `otelhttp`, `otelgin`, `otelmacaron`, `otelrestful` middlewares by using `SpanKindServer` when deciding the `SpanStatus`. This makes `4xx` response codes to not be an error anymore. (#2427) ## [1.7.0/0.32.0] - 2022-04-28 ### Added - Consistent probability sampler implementation. (#1379) ### Changed - Upgraded all `semconv` package use to `v1.10.0`. This includes a backwards incompatible change for the `otelgocql` package to conform with the specification [change](https://github.com/open-telemetry/opentelemetry-specification/pull/1973). The `db.cassandra.keyspace` attribute is now transmitted as the `db.name` attribute. (#2222) ### Fixed - Fix the `otelmux` middleware by using `SpanKindServer` when deciding the `SpanStatus`. This makes `4xx` response codes to not be an error anymore. (#1973) - Fixed jaegerremote sampler not behaving properly with per operation strategy set. (#2137) - Stopped injecting propagation context into response headers in otelhttp. (#2180) - Fix issue where attributes for DynamoDB were not added because of a string miss match. (#2272) ### Removed - Drop support for Go 1.16. The project currently only supports Go 1.17 and above. (#2314) ## [1.6.0/0.31.0] - 2022-03-28 ### Added - The project is now tested against Go 1.18 (in addition to the existing 1.16 and 1.17) (#1976) ### Changed - Upgraded all dependencies on stable modules from `go.opentelemetry.io/otel` from v1.5.0 to v1.6.1. (#2134) - Upgraded all dependencies on metric modules from `go.opentelemetry.io/otel` from v0.27.0 to v0.28.0. (#1977) ### Fixed - otelhttp: Avoid panic by adding nil check to `wrappedBody.Close` (#2164) ## [1.5.0/0.30.0/0.1.0] - 2022-03-16 ### Added - Added the `go.opentelemetry.io/contrib/samplers/jaegerremote` package. This package implements the Jaeger remote sampler for OpenTelemetry Go. (#936) - DynamoDB spans created with the `go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws` package now have the appropriate database attributes added for the operation being performed. These attributes are detected automatically, but it is also now possible to provide a custom function to set attributes using `WithAttributeSetter`. (#1582) - Add resource detector for GCP cloud function. (#1584) - Add OpenTracing baggage extraction to the OpenTracing propagator in `go.opentelemetry.io/contrib/propagators/ot`. (#1880) ### Fixed - Fix the `echo` middleware by using `SpanKind.SERVER` when deciding the `SpanStatus`. This makes `4xx` response codes to not be an error anymore. (#1848) ### Removed - The deprecated `go.opentelemetry.io/contrib/exporters/metric/datadog` module is removed. (#1920) - The deprecated `go.opentelemetry.io/contrib/exporters/metric/dogstatsd` module is removed. (#1920) - The deprecated `go.opentelemetry.io/contrib/exporters/metric/cortex` module is removed. Use the `go.opentelemetry.io/otel/exporters/otlp/otlpmetric` exporter as a replacement to send data to a collector which can then export with its PRW exporter. (#1920) ## [1.4.0/0.29.0] - 2022-02-14 ### Added - Add `WithClientTrace` option to `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`. (#875) ### Changed - All metric instruments from the `go.opentelemetry.io/contrib/instrumentation/runtime` package have been renamed from `runtime.go.*` to `process.runtime.go.*` so as to comply with OpenTelemetry semantic conventions. (#1549) ### Fixed - Change the `http-server-duration` instrument in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` to record milliseconds instead of microseconds. This changes fixes the code to comply with the OpenTelemetry specification. (#1414, #1537) - Fixed the region reported by the `"go.opentelemetry.io/contrib/detectors/gcp".CloudRun` detector to comply with the OpenTelemetry specification. It no longer includes the project scoped region path, instead just the region. (#1546) - The `"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp".Transport` type now correctly handles protocol switching responses. The returned response body implements the `io.ReadWriteCloser` interface if the underlying one does. This ensures that protocol switching requests receive a response body that they can write to. (#1329, #1628) ### Deprecated - The `go.opentelemetry.io/contrib/exporters/metric/datadog` module is deprecated. (#1639) - The `go.opentelemetry.io/contrib/exporters/metric/dogstatsd` module is deprecated. (#1639) - The `go.opentelemetry.io/contrib/exporters/metric/cortex` module is deprecated. Use the go.opentelemetry.io/otel/exporters/otlp/otlpmetric exporter as a replacement to send data to a collector which can then export with its PRW exporter. (#1639) ### Removed - Remove the `MinMaxSumCount` from cortex and datadog exporter. (#1554) - The `go.opentelemetry.io/contrib/exporters/metric/dogstatsd` exporter no longer support exporting histogram or exact data points. (#1639) - The `go.opentelemetry.io/contrib/exporters/metric/datadog` exporter no longer support exporting exact data points. (#1639) ## [1.3.0/0.28.0] - 2021-12-10 ### âš ï¸ Notice âš ï¸ We have updated the project minimum supported Go version to 1.16 ### Changed - `otelhttptrace.NewClientTrace` now uses `TracerProvider` from the parent context if one exists and none was set with `WithTracerProvider` (#874) ### Fixed - The `"go.opentelemetry.io/contrib/detector/aws/ecs".Detector` no longer errors if not running in ECS. (#1428) - `go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux` does not require instrumented HTTP handlers to call `Write` nor `WriteHeader` anymore. (#1443) ## [1.2.0/0.27.0] - 2021-11-15 ### Changed - Update dependency on the `go.opentelemetry.io/otel` project to `v1.2.0`. - `go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda/xrayconfig` updated to ensure access to the `TracerProvider`. - A `NewTracerProvider()` function is available to construct a recommended `TracerProvider` configuration. - `AllRecommendedOptions()` has been renamed to `WithRecommendedOptions()` and takes a `TracerProvider` as an argument. - `EventToCarrier()` and `Propagator()` are now `WithEventToCarrier()` and `WithPropagator()` to reflect that they return `Option` implementations. ## [1.1.1/0.26.1] - 2021-11-04 ### Changed - The `Transport`, `Handler`, and HTTP client convenience wrappers in the `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` package now use the `TracerProvider` from the parent context if one exists and none was explicitly set when configuring the instrumentation. (#873) - Semantic conventions now use `go.opentelemetry.io/otel/semconv/v1.7.0"`. (#1385) ## [1.1.0/0.26.0] - 2021-10-28 Update dependency on the `go.opentelemetry.io/otel` project to `v1.1.0`. ### Added - Add instrumentation for the `github.com/aws/aws-lambda-go` package. (#983) - Add resource detector for AWS Lambda. (#983) - Add `WithTracerProvider` option for `otelhttptrace.NewClientTrace`. (#1128) - Add optional AWS X-Ray configuration module for AWS Lambda Instrumentation. (#984) ### Fixed - The `go.opentelemetry.io/contrib/propagators/ot` propagator returns the words `true` or `false` for the `ot-tracer-sampled` header instead of numerical `0` and `1`. (#1358) ## [1.0.0/0.25.0] - 2021-10-06 - Resource detectors and propagators (with the exception of `go. opentelemetry.io/contrib/propagators/opencensus`) are now stable and released at v1.0.0. - Update dependency on the `go.opentelemetry.io/otel` project to `v1.0.1`. - Update dependency on `go.opentelemetry.io/otel/metric` to `v0.24.0`. ## [0.24.0] - 2021-09-21 - Update dependency on the `go.opentelemetry.io/otel` project to `v1.0.0`. ## [0.23.0] - 2021-09-08 ### Added - Add `WithoutSubSpans`, `WithRedactedHeaders`, `WithoutHeaders`, and `WithInsecureHeaders` options for `otelhttptrace.NewClientTrace`. (#879) ### Changed - Split `go.opentelemetry.io/contrib/propagators` module into `b3`, `jaeger`, `ot` modules. (#985) - `otelmongodb` span attributes, name and span status now conform to specification. (#769) - Migrated EC2 resource detector support from root module `go.opentelemetry.io/contrib/detectors/aws` to a separate EC2 resource detector module `go.opentelemetry.io/contrib/detectors/aws/ec2` (#1017) - Add `cloud.provider` and `cloud.platform` to AWS detectors. (#1043) - `otelhttptrace.NewClientTrace` now redacts known sensitive headers by default. (#879) ### Fixed - Fix span not marked as error in `otelhttp.Transport` when `RoundTrip` fails with an error. (#950) ## [0.22.0] - 2021-07-26 ### Added - Add the `zpages` span processor. (#894) ### Changed - The `b3.B3` type has been removed. `b3.New()` and `b3.WithInjectEncoding(encoding)` are added to replace it. (#868) ### Fixed - Fix deadlocks and race conditions in `otelsarama.WrapAsyncProducer`. The `messaging.message_id` and `messaging.kafka.partition` attributes are now not set if a message was not processed. (#754) (#755) (#881) - Fix `otelsarama.WrapAsyncProducer` so that the messages from the `Errors` channel contain the original `Metadata`. (#754) ## [0.21.0] - 2021-06-18 ### Fixed - Dockerfile based examples for `otelgin` and `otelmacaron`. (#767) ### Changed - Supported minimum version of Go bumped from 1.14 to 1.15. (#787) - EKS Resource Detector now use the Kubernetes Go client to obtain the ConfigMap. (#813) ### Removed - Remove service name from `otelmongodb` configuration and span attributes. (#763) ## [0.20.0] - 2021-04-23 ### Changed - The `go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo` instrumentation now accepts a `WithCommandAttributeDisabled`, so the caller can specify whether to opt-out of tracing the mongo command. (#712) - Upgrade to v0.20.0 of `go.opentelemetry.io/otel`. (#758) - The B3 and Jaeger propagators now store their debug or deferred state in the context.Context instead of the SpanContext. (#758) ## [0.19.0] - 2021-03-19 ### Changed - Upgrade to v0.19.0 of `go.opentelemetry.io/otel`. - Fix Span names created in HTTP Instrumentation package to conform with guidelines. (#757) ## [0.18.0] - 2021-03-04 ### Fixed - `otelmemcache` no longer sets span status to OK instead of leaving it unset. (#477) - Fix goroutine leak in gRPC `StreamClientInterceptor`. (#581) ### Removed - Remove service name from `otelmemcache` configuration and span attributes. (#477) ## [0.17.0] - 2021-02-15 ### Added - Add `ot-tracer` propagator (#562) ### Changed - Rename project default branch from `master` to `main`. ### Fixed - Added failure message for AWS ECS resource detector for better debugging (#568) - Goroutine leak in gRPC StreamClientInterceptor while streamer returns an error. (#581) ## [0.16.0] - 2021-01-13 ### Fixed - Fix module path for AWS ECS resource detector (#517) ## [0.15.1] - 2020-12-14 ### Added - Add registry link check to `Makefile` and pre-release script. (#446) - A new AWS X-Ray ID Generator (#459) - Migrate CircleCI jobs to GitHub Actions (#476) - Add CodeQL GitHub Action (#506) - Add gosec workflow to GitHub Actions (#507) ### Fixed - Fixes the body replacement in otelhttp to not to mutate a nil body. (#484) ## [0.15.0] - 2020-12-11 ### Added - A new Amazon EKS resource detector. (#465) - A new `gcp.CloudRun` detector for detecting resource from a Cloud Run instance. (#455) ## [0.14.0] - 2020-11-20 ### Added - `otelhttp.{Get,Head,Post,PostForm}` convenience wrappers for their `http` counterparts. (#390) - The AWS detector now adds the cloud zone, host image ID, host type, and host name to the returned `Resource`. (#410) - Add Amazon ECS Resource Detector for AWS X-Ray. (#466) - Add propagator for AWS X-Ray (#462) ### Changed - Add semantic version to `Tracer` / `Meter` created by instrumentation packages `otelsaram`, `otelrestful`, `otelmongo`, `otelhttp` and `otelhttptrace`. (#412) - Update instrumentation guidelines about tracer / meter semantic version. (#412) - Replace internal tracer and meter helpers by helpers from `go.opentelemetry.io/otel`. (#414) - gRPC instrumentation sets span attribute `rpc.grpc.status_code`. (#453) ## Fixed - `/detectors/aws` no longer fails if instance metadata is not available (e.g. not running in AWS) (#401) - The AWS detector now returns a partial resource and an appropriate error if it encounters an error part way through determining a `Resource` identity. (#410) - The `host` instrumentation unit test has been updated to not depend on the system it runs on. (#426) ## [0.13.0] - 2020-10-09 ## Added - A Jaeger propagator. (#375) ## Changed - The `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc` package instrumentation no longer accepts a `Tracer` as an argument to the interceptor function. Instead, a new `WithTracerProvider` option is added to configure the `TracerProvider` used when creating the `Tracer` for the instrumentation. (#373) - The `go.opentelemetry.io/contrib/instrumentation/gopkg.in/macaron.v1/otelmacaron` instrumentation now accepts a `TracerProvider` rather than a `Tracer`. (#374) - Remove `go.opentelemetry.io/otel/sdk` dependency from instrumentation. (#381) - Use `httpsnoop` in `go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux` to ensure `http.ResponseWriter` additional interfaces are preserved. (#388) ### Fixed - The `go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho.Middleware` no longer sends duplicate errors to the global `ErrorHandler`. (#377, #364) - The import comment in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` is now correctly quoted. (#379) - The B3 propagator sets the sample bitmask when the sampling decision is `debug`. (#369) ## [0.12.0] - 2020-09-25 ### Added - Benchmark tests for the gRPC instrumentation. (#296) - Integration testing for the gRPC instrumentation. (#297) - Allow custom labels to be added to net/http metrics. (#306) - Added B3 propagator, moving it out of open.telemetry.io/otel repo. (#344) ### Changed - Unify instrumentation about provider options for `go.mongodb.org/mongo-driver`, `gin-gonic/gin`, `gorilla/mux`, `labstack/echo`, `emicklei/go-restful`, `bradfitz/gomemcache`, `Shopify/sarama`, `net/http` and `beego`. (#303) - Update instrumentation guidelines about uniform provider options. Also, update style guide. (#303) - Make config struct of instrumentation unexported. (#303) - Instrumentations have been updated to adhere to the [configuration style guide's](https://github.com/open-telemetry/opentelemetry-go/blob/master/CONTRIBUTING.md#config) updated recommendation to use `newConfig()` instead of `configure()`. (#336) - A new instrumentation naming scheme is implemented to avoid package name conflicts for instrumented packages while still remaining discoverable. (#359) - `google.golang.org/grpc` -> `google.golang.org/grpc/otelgrpc` - `go.mongodb.org/mongo-driver` -> `go.mongodb.org/mongo-driver/mongo/otelmongo` - `net/http` -> `net/http/otelhttp` - `net/http/httptrace` -> `net/http/httptrace/otelhttptrace` - `github.com/labstack/echo` -> `github.com/labstack/echo/otelecho` - `github.com/bradfitz/gomemcache` -> `github.com/bradfitz/gomemcache/memcache/otelmemcache` - `github.com/gin-gonic/gin` -> `github.com/gin-gonic/gin/otelgin` - `github.com/gocql/gocql` -> `github.com/gocql/gocql/otelgocql` - `github.com/emicklei/go-restful` -> `github.com/emicklei/go-restful/otelrestful` - `github.com/Shopify/sarama` -> `github.com/Shopify/sarama/otelsarama` - `github.com/gorilla/mux` -> `github.com/gorilla/mux/otelmux` - `github.com/astaxie/beego` -> `github.com/astaxie/beego/otelbeego` - `gopkg.in/macaron.v1` -> `gopkg.in/macaron.v1/otelmacaron` - Rename `OTelBeegoHandler` to `Handler` in the `go.opentelemetry.io/contrib/instrumentation/github.com/astaxie/beego/otelbeego` package. (#359) - Replace `WithTracer` with `WithTracerProvider` in the `go.opentelemetry.io/contrib/instrumentation/gopkg.in/macaron.v1/otelmacaron` instrumentation. (#374) ## [0.11.0] - 2020-08-25 ### Added - Top-level `Version()` and `SemVersion()` functions defining the current version of the contrib package. (#225) - Instrumentation for the `github.com/astaxie/beego` package. (#200) - Instrumentation for the `github.com/bradfitz/gomemcache` package. (#204) - Host metrics instrumentation. (#231) - Cortex histogram and distribution support. (#237) - Cortex example project. (#238) - Cortex HTTP authentication. (#246) ### Changed - Remove service name as a parameter of Sarama instrumentation. (#221) - Replace `WithTracer` with `WithTracerProvider` in Sarama instrumentation. (#221) - Switch to use common top-level module `SemVersion()` when creating versioned tracer in `bradfitz/gomemcache`. (#226) - Use `IntegrationShouldRun` in `gomemcache_test`. (#254) - Use Go 1.15 for CI builds. (#236) - Improved configuration for `runtime` instrumentation. (#224) ### Fixed - Update dependabot configuration to include newly added `bradfitz/gomemcache` package. (#226) - Correct `runtime` instrumentation name. (#241) ## [0.10.1] - 2020-08-13 ### Added - The `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc` module has been added to replace the instrumentation that had previoiusly existed in the `go.opentelemetry.io/otel/instrumentation/grpctrace` package. (#189) - Instrumentation for the stdlib `net/http` and `net/http/httptrace` packages. (#190) - Initial Cortex exporter. (#202, #205, #210, #211, #215) ### Fixed - Bump google.golang.org/grpc from 1.30.0 to 1.31.0. (#166) - Bump go.mongodb.org/mongo-driver from 1.3.5 to 1.4.0 in /instrumentation/go.mongodb.org/mongo-driver. (#170) - Bump google.golang.org/grpc in /instrumentation/github.com/gin-gonic/gin. (#173) - Bump google.golang.org/grpc in /instrumentation/github.com/labstack/echo. (#176) - Bump google.golang.org/grpc from 1.30.0 to 1.31.0 in /instrumentation/github.com/Shopify/sarama. (#179) - Bump cloud.google.com/go from 0.61.0 to 0.63.0 in /detectors/gcp. (#181, #199) - Bump github.com/aws/aws-sdk-go from 1.33.15 to 1.34.1 in /detectors/aws. (#184, #192, #193, #198, #201, #203) - Bump github.com/golangci/golangci-lint from 1.29.0 to 1.30.0 in /tools. (#186) - Setup CI to run tests that require external resources (Cassandra and MongoDB). (#191) - Bump github.com/Shopify/sarama from 1.26.4 to 1.27.0 in /instrumentation/github.com/Shopify/sarama. (#206) ## [0.10.0] - 2020-07-31 This release upgrades its [go.opentelemetry.io/otel](https://github.com/open-telemetry/opentelemetry-go/releases/tag/v0.10.0) dependency to v0.10.0 and includes new instrumentation for popular Kafka and Cassandra clients. ### Added - A detector that generate resources from GCE instance. (#132) - A detector that generate resources from AWS instances. (#139) - Instrumentation for the Kafka client github.com/Shopify/sarama. (#134, #153) - Links and status message for mock span in the internal testing library. (#134) - Instrumentation for the Cassandra client github.com/gocql/gocql. (#137) - A detector that generate resources from GKE clusters. (#154) ### Fixed - Bump github.com/aws/aws-sdk-go from 1.33.8 to 1.33.15 in /detectors/aws. (#155, #157, #159, #162) - Bump github.com/golangci/golangci-lint from 1.28.3 to 1.29.0 in /tools. (#146) ## [0.9.0] - 2020-07-20 This release upgrades its [go.opentelemetry.io/otel](https://github.com/open-telemetry/opentelemetry-go/releases/tag/v0.9.0) dependency to v0.9.0. ### Fixed - Bump github.com/emicklei/go-restful/v3 from 3.0.0 to 3.2.0 in /instrumentation/github.com/emicklei/go-restful. (#133) - Update dependabot configuration to correctly check all included packages. (#131) - Update `RELEASING.md` with correct `tag.sh` command. (#130) ## [0.8.0] - 2020-07-10 This release upgrades its [go.opentelemetry.io/otel](https://github.com/open-telemetry/opentelemetry-go/releases/tag/v0.8.0) dependency to v0.8.0, includes minor fixes, and new instrumentation. ### Added - Create this `CHANGELOG.md`. (#114) - Add `emicklei/go-restful/v3` trace instrumentation. (#115) ### Changed - Update `CONTRIBUTING.md` to ask for updates to `CHANGELOG.md` with each pull request. (#114) - Move all `github.com` package instrumentation under a `github.com` directory. (#118) ### Fixed - Update README to include information about external instrumentation. To start, this includes native instrumentation found in the `go-redis/redis` package. (#117) - Bump github.com/golangci/golangci-lint from 1.27.0 to 1.28.2 in /tools. (#122, #123, #125) - Bump go.mongodb.org/mongo-driver from 1.3.4 to 1.3.5 in /instrumentation/go.mongodb.org/mongo-driver. (#124) ## [0.7.0] - 2020-06-29 This release upgrades its [go.opentelemetry.io/otel](https://github.com/open-telemetry/opentelemetry-go/releases/tag/v0.7.0) dependency to v0.7.0. ### Added - Create `RELEASING.md` instructions. (#101) - Apply transitive dependabot go.mod updates as part of a new automatic Github workflow. (#94) - New dependabot integration to automate package upgrades. (#61) - Add automatic tag generation script for release. (#60) ### Changed - Upgrade Datadog metrics exporter to include Resource tags. (#46) - Added output validation to Datadog example. (#96) - Move Macaron package to match layout guidelines. (#92) - Update top-level README and instrumentation README. (#92) - Bump google.golang.org/grpc from 1.29.1 to 1.30.0. (#99) - Bump github.com/golangci/golangci-lint from 1.21.0 to 1.27.0 in /tools. (#77) - Bump go.mongodb.org/mongo-driver from 1.3.2 to 1.3.4 in /instrumentation/go.mongodb.org/mongo-driver. (#76) - Bump github.com/stretchr/testify from 1.5.1 to 1.6.1. (#74) - Bump gopkg.in/macaron.v1 from 1.3.5 to 1.3.9 in /instrumentation/macaron. (#68) - Bump github.com/gin-gonic/gin from 1.6.2 to 1.6.3 in /instrumentation/gin-gonic/gin. (#73) - Bump github.com/DataDog/datadog-go from 3.5.0+incompatible to 3.7.2+incompatible in /exporters/metric/datadog. (#78) - Replaced `internal/trace/http.go` helpers with `api/standard` helpers from otel-go repo. (#112) ## [0.6.1] - 2020-06-08 First official tagged release of `contrib` repository. ### Added - `labstack/echo` trace instrumentation (#42) - `mongodb` trace instrumentation (#26) - Go Runtime metrics (#9) - `gorilla/mux` trace instrumentation (#19) - `gin-gonic` trace instrumentation (#15) - `macaron` trace instrumentation (#20) - `dogstatsd` metrics exporter (#10) - `datadog` metrics exporter (#22) - Tags to all modules in repository - Repository folder structure and automated build (#3) ### Changes - Prefix support for dogstatsd (#34) - Update Go Runtime package to use batch observer (#44) [Unreleased]: https://github.com/open-telemetry/opentelemetry-go-contrib/compare/v1.31.0...HEAD [1.31.0/0.56.0/0.25.0/0.11.0/0.6.0/0.4.0/0.3.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.31.0 [1.30.0/0.55.0/0.24.0/0.10.0/0.5.0/0.3.0/0.2.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.30.0 [1.29.0/0.54.0/0.23.0/0.9.0/0.4.0/0.2.0/0.1.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.29.0 [1.28.0/0.53.0/0.22.0/0.8.0/0.3.0/0.1.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.28.0 [1.27.0/0.52.0/0.21.0/0.7.0/0.2.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.27.0 [1.26.0/0.51.0/0.20.0/0.6.0/0.1.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.26.0 [1.25.0/0.50.0/0.19.0/0.5.0/0.0.1]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.25.0 [1.24.0/0.49.0/0.18.0/0.4.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.24.0 [1.23.0/0.48.0/0.17.0/0.3.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.23.0 [1.22.0/0.47.0/0.16.0/0.2.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.22.0 [1.21.1/0.46.1/0.15.1/0.1.1]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.21.1 [1.21.0/0.46.0/0.15.0/0.1.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.21.0 [1.20.0/0.45.0/0.14.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.20.0 [1.19.0/0.44.0/0.13.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.19.0 [1.18.0/0.43.0/0.12.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.18.0 [1.17.0/0.42.0/0.11.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.17.0 [1.17.0-rc.1/0.42.0-rc.1/0.11.0-rc.1]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.17.0-rc.1 [1.16.1/0.41.1/0.10.1]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.16.1 [1.16.0/0.41.0/0.10.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.16.0 [1.16.0-rc.2/0.41.0-rc.2/0.10.0-rc.2]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.16.0-rc.2 [1.16.0-rc.1/0.41.0-rc.1/0.10.0-rc.1]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.16.0-rc.1 [1.15.0/0.40.0/0.9.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.15.0 [1.14.0/0.39.0/0.8.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.14.0 [1.13.0/0.38.0/0.7.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.13.0 [1.12.0/0.37.0/0.6.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.12.0 [1.11.1/0.36.4/0.5.2]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.11.1 [1.11.0/0.36.3/0.5.1]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.11.0 [0.36.2]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/zpages/v0.36.2 [0.36.1]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/zpages/v0.36.1 [0.36.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/zpages/v0.36.0 [1.10.0/0.35.0/0.5.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.10.0 [1.9.0/0.34.0/0.4.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.9.0 [1.8.0/0.33.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.8.0 [1.7.0/0.32.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.7.0 [1.6.0/0.31.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.6.0 [1.5.0/0.30.0/0.1.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.5.0 [1.4.0/0.29.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.4.0 [1.3.0/0.28.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.3.0 [1.2.0/0.27.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.2.0 [1.1.1/0.26.1]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.1.1 [1.1.0/0.26.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.1.0 [1.0.0/0.25.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v1.0.0 [0.24.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.24.0 [0.23.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.23.0 [0.22.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.22.0 [0.21.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.21.0 [0.20.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.20.0 [0.19.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.19.0 [0.18.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.18.0 [0.17.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.17.0 [0.16.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.16.0 [0.15.1]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.15.1 [0.15.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.15.0 [0.14.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.14.0 [0.13.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.13.0 [0.12.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.12.0 [0.11.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.11.0 [0.10.1]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.10.1 [0.10.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.10.0 [0.9.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.9.0 [0.8.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.8.0 [0.7.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.7.0 [0.6.1]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.6.1 [Go 1.23]: https://go.dev/doc/go1.23 [Go 1.22]: https://go.dev/doc/go1.22 [Go 1.21]: https://go.dev/doc/go1.21 [Go 1.20]: https://go.dev/doc/go1.20 [Go 1.19]: https://go.dev/doc/go1.19 [Go 1.18]: https://go.dev/doc/go1.18 [GO-2024-2687]: https://pkg.go.dev/vuln/GO-2024-2687 open-telemetry-opentelemetry-go-contrib-e5abccb/CODEOWNERS000066400000000000000000000122611470323427300236460ustar00rootroot00000000000000##################################################### # # List of approvers for this repository # ##################################################### # # Learn about membership in OpenTelemetry community: # https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#member # # Learn about Code Owners policy in OpenTelemetry Go Contrib: # https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/CONTRIBUTING.md#code-owners # # # Learn about CODEOWNERS file format: # https://help.github.com/en/articles/about-code-owners # # NOTE: Lines should be entered in the following format: # /.. # instrumentation/net/http/otelhttp/ @open-telemetry/collector-go-approvers @madvikinggod @mralias # Path separator and minimum of 1 space between component path and owners is # important for validation steps # * @open-telemetry/go-approvers CODEOWNERS @MrAlias @pellared @dashpole @XSAM @dmathieu bridges/otelslog @open-telemetry/go-approvers @MrAlias @pellared bridges/otellogrus/ @open-telemetry/go-approvers @dmathieu @pellared bridges/prometheus/ @open-telemetry/go-approvers @dashpole bridges/otelzap/ @open-telemetry/go-approvers @pellared @khushijain21 bridges/otellogr/ @open-telemetry/go-approvers @scorpionknifes @pellared config/ @open-telemetry/go-approvers @pellared @codeboten detectors/aws/ec2 @open-telemetry/go-approvers @pyohannes @akats7 detectors/aws/ecs @open-telemetry/go-approvers @pyohannes @akats7 detectors/aws/eks @open-telemetry/go-approvers @pyohannes detectors/aws/lambda @open-telemetry/go-approvers @akats7 detectors/azure/ @open-telemetry/go-approvers @pyohannes detectors/gcp/ @open-telemetry/go-approvers @dashpole exporters/autoexport @open-telemetry/go-approvers @MikeGoldsmith @pellared instrumentation/github.com/aws/aws-lambda-go/otellambda/ @open-telemetry/go-approvers @akats7 instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/ @open-telemetry/go-approvers @akats7 instrumentation/github.com/emicklei/go-restful/otelrestful/ @open-telemetry/go-approvers @dashpole instrumentation/github.com/gin-gonic/gin/otelgin/ @open-telemetry/go-approvers instrumentation/github.com/gorilla/mux/otelmux/ @open-telemetry/go-approvers @akats7 instrumentation/github.com/labstack/echo/otelecho/ @open-telemetry/go-approvers @scorpionknifes instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo/ @open-telemetry/go-approvers instrumentation/google.golang.org/grpc/otelgrpc/ @open-telemetry/go-approvers @dashpole instrumentation/host/ @open-telemetry/go-approvers @dmathieu instrumentation/net/http/httptrace/otelhttptrace/ @open-telemetry/go-approvers @dmathieu instrumentation/net/http/otelhttp/ @open-telemetry/go-approvers @dmathieu instrumentation/runtime/ @open-telemetry/go-approvers @dmathieu @dashpole processors/baggagecopy @open-telemetry/go-approvers @codeboten @MikeGoldsmith processors/minsev @open-telemetry/go-approvers @MrAlias propagators/autoprop/ @open-telemetry/go-approvers @MrAlias propagators/aws/ @open-telemetry/go-approvers @akats7 propagators/b3/ @open-telemetry/go-approvers @pellared propagators/jaeger/ @open-telemetry/go-approvers @yurishkuro propagators/opencensus/ @open-telemetry/go-approvers @dashpole propagators/ot/ @open-telemetry/go-approvers @pellared samplers/jaegerremote/ @open-telemetry/go-approvers @yurishkuro samplers/probability/consistent/ @open-telemetry/go-approvers zpages/ @open-telemetry/go-approvers @dashpole instrgen/ @open-telemetry/go-approvers @open-telemetry/go-instrumentation-approvers @MrAlias @pdelewski open-telemetry-opentelemetry-go-contrib-e5abccb/CONTRIBUTING.md000066400000000000000000000204401470323427300245020ustar00rootroot00000000000000# Contributing to opentelemetry-go-contrib Welcome to the OpenTelemetry Go contrib repository! Thank you for your interest in contributing to this project. Before you start please be sure to read through these contributing requirements and recommendations. ## Become a Contributor All contributions to this project MUST be licensed under this project's [license](LICENSE). You will need to sign the [CNCF CLA](https://identity.linuxfoundation.org/projects/cncf) before your contributions will be accepted. ## Code Owners To ensure code that lives in this repository is not abandoned, all modules added are required to have a Code Owner. A Code Owner is responsible for a module within this repository. This status is identified in the [CODEOWNERS file](./CODEOWNERS). That responsibility includes maintaining the component, triaging and responding to issues, and reviewing pull requests. ### Requirements To become a Code Owner, you will need to meet the following requirements. 1. You will need to be a [member of the OpenTelemetry organization] and maintain that membership. 2. You need to have good working knowledge of the code you are sponsoring and any project that that code instruments or is based on. If you are not an existing member, this is not an immediate disqualification. You will need to engage with the OpenTelemetry community so you can achieve this membership in the process of becoming a Code Owner. It is best to have resolved at least an issue related to the module, contributed directly to the module, and/or reviewed module PRs. How much interaction with the module is required before becoming a Code Owner is up to the existing Code Owners. Code Ownership is ultimately up to the judgement of the existing Code Owners and Maintainers of this repository. Meeting the above requirements is not a guarantee to be granted Code Ownership. [member of the OpenTelemetry organization]: https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#member ### Responsibilities As a Code Owner you will be responsible for the following: - You will be responsible for keeping up with the instrumented library. Any "upstream" changes that impact this module need to be proactively handle by you. - You will be expected to review any Pull Requests or Issues created that relate to this module. - You will be responsible for the stability and versioning compliance of the module. - You will be responsible for deciding any additional Code Owners of the module. ### How to become a Code Owner To become a Code Owner, open [an Issue](https://github.com/open-telemetry/opentelemetry-go-contrib/issues/new?assignees=&labels=&projects=&template=owner.md&title=). ### Removing Code Ownership Code Owners are expected to remove their ownership if they cannot fulfill their responsibilities anymore. It is at the discretion of the repository Maintainers and fellow Code Owners to decide if a Code Owner should be considered for removal. If a Code Owner is determined to be unable to perform their duty, a repository Maintainer will remove their ownership. Inactivity greater than 5 months, during which time there are active Issues or Pull Requests to address, is deemed an automatic disqualification from being a Code Owner. A repository Maintainer may remove an Code Owner inactive for this length. ## Filing Issues Sensitive security-related issues should be reported to . See the [security policy](https://github.com/open-telemetry/opentelemetry-go-contrib/security/policy) for details. When reporting bugs, please be sure to include the following. - What version of Go and opentelemetry-go-contrib are you using? - What operating system and processor architecture are you using? - What did you do? - What did you expect to see? - What did you see instead? For instrumentation requests, please see the [instrumentation documentation](./instrumentation/README.md#new-instrumentation). ## Contributing Code The project welcomes code patches, but to make sure things are well coordinated you should discuss any significant change before starting the work. It's recommended that you signal your intention to contribute in the issue tracker, either by [filing a new issue](https://github.com/open-telemetry/opentelemetry-go-contrib/issues/new) or by claiming an [existing one](https://github.com/open-telemetry/opentelemetry-go-contrib/issues). ### Style Guide * Code should conform to the [opentelemetry-go Style Guide](https://github.com/open-telemetry/opentelemetry-go/blob/main/CONTRIBUTING.md#style-guide). * Make sure to run `make precommit` - this will find and fix issues with the code formatting. ### Pull Requests All pull requests need to be made from [a fork](https://docs.github.com/en/get-started/quickstart/fork-a-repo) of this repository. Changes should be made using [the GitHub flow](https://guides.github.com/introduction/flow/) and submitted as a pull request to this repository. A pull request is considered ready to merge when the following criteria are meet. * It has received two approvals from Approvers/Maintainers (at different companies), unless the change is for an exempt module[^1]. * All feedback has been addressed. Be sure to "Resolve" all comments that have been addressed to signal this. * Any substantive changes submitted after an Approval removes that Approval. You will need to manually clear these prior Approval reviews to indicate to the reviewer that they need to resubmit their review. This includes changes resulting from other feedback. Unless the approver explicitly stated that their approval will persist across changes it should be assumed that the pull request needs their review again. Other project members (e.g. approvers, maintainers) can help with this if there are any questions or if you forget to clear reviews. * If the changes are not trivial, cosmetic, exempt[^1], or for documentation or dependencies only, the pull request will need to be open for review for at least one working day. This gives people reasonable time to review. * `CHANGELOG.md` has been updated to reflect what has been added, changed, removed, or fixed from the end users perspective. See [how to keep a changelog](https://keepachangelog.com/en/1.0.0/). * Urgent fixes can take exception as long as it has been actively communicated. Any Maintainer can merge the pull request once it is ready to merge. [^1]: The `go.opentelemetry.io/contrib/instrgen` module is exempt from the two approvals and one day requirement. Only one approval is needed to merge a Pull Request for that module and there is no minimum amount of time required for the PR to be open before merging. This exemption is to be removed when that package makes its first tagged release. ### Draft Pull Requests It can be helpful at times to publish your incomplete changes. To do this create [a draft pull request](https://github.blog/2019-02-14-introducing-draft-pull-requests/). Additionally, you can prefix the pull request title with `[WIP]`. ## Where to Get Help You can connect with us in our [slack channel](https://cloud-native.slack.com/archives/C01NPAXACKT). The Go special interest group (SIG) meets regularly. See the OpenTelemetry [community](https://github.com/open-telemetry/community#golang-sdk) repo for information on this and other language SIGs. See the [public meeting notes](https://docs.google.com/document/d/1E5e7Ld0NuU1iVvf-42tOBpu2VBBLYnh73GJuITGJTTU/edit#heading=h.ru7kpkv1rxlh) for a summary description of past meetings. ## Approvers and Maintainers Approvers: Maintainers: - [Damien Mathieu](https://github.com/dmathieu), Elastic - [David Ashpole](https://github.com/dashpole), Google - [Robert PajÄ…k](https://github.com/pellared), Splunk - [Sam Xie](https://github.com/XSAM), Cisco/AppDynamics - [Tyler Yahn](https://github.com/MrAlias), Splunk Emeritus: - [Aaron Clawson](https://github.com/MadVikingGod), LightStep - [Anthony Mirabella](https://github.com/Aneurysm9), Amazon - [Chester Cheung](https://github.com/hanyuancheung), Tencent - [Evan Torrie](https://github.com/evantorrie), Yahoo - [Gustavo Silva Paiva](https://github.com/paivagustavo), LightStep - [Josh MacDonald](https://github.com/jmacd), LightStep ### Become an Approver or a Maintainer See the [community membership document in OpenTelemetry community repo](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md). open-telemetry-opentelemetry-go-contrib-e5abccb/LICENSE000066400000000000000000000261351470323427300232650ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. open-telemetry-opentelemetry-go-contrib-e5abccb/Makefile000066400000000000000000000253261470323427300237210ustar00rootroot00000000000000# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 TOOLS_MOD_DIR := ./tools ALL_DOCS := $(shell find . -name '*.md' -type f | sort) ALL_GO_MOD_DIRS := $(shell find . -type f -name 'go.mod' -exec dirname {} \; | sort) OTEL_GO_MOD_DIRS := $(filter-out $(TOOLS_MOD_DIR), $(ALL_GO_MOD_DIRS)) ALL_COVERAGE_MOD_DIRS := $(shell find . -type f -name 'go.mod' -exec dirname {} \; | grep -E -v '^./example|^$(TOOLS_MOD_DIR)' | sort) # URLs to check if all contrib entries exist in the registry. REGISTRY_BASE_URL = https://raw.githubusercontent.com/open-telemetry/opentelemetry.io/main/content/en/registry CONTRIB_REPO_URL = https://github.com/open-telemetry/opentelemetry-go-contrib/tree/main GO = go TIMEOUT = 60 .DEFAULT_GOAL := precommit .PHONY: precommit ci precommit: generate license-check misspell go-mod-tidy golangci-lint-fix test-default ci: generate license-check lint vanity-import-check build test-default check-clean-work-tree test-coverage # Tools .PHONY: tools TOOLS = $(CURDIR)/.tools $(TOOLS): @mkdir -p $@ $(TOOLS)/%: $(TOOLS_MOD_DIR)/go.mod | $(TOOLS) cd $(TOOLS_MOD_DIR) && \ $(GO) build -o $@ $(PACKAGE) GOLANGCI_LINT = $(TOOLS)/golangci-lint $(GOLANGCI_LINT): PACKAGE=github.com/golangci/golangci-lint/cmd/golangci-lint MISSPELL = $(TOOLS)/misspell $(MISSPELL): PACKAGE=github.com/client9/misspell/cmd/misspell GOCOVMERGE = $(TOOLS)/gocovmerge $(GOCOVMERGE): PACKAGE=github.com/wadey/gocovmerge STRINGER = $(TOOLS)/stringer $(STRINGER): PACKAGE=golang.org/x/tools/cmd/stringer PORTO = $(TOOLS)/porto $(TOOLS)/porto: PACKAGE=github.com/jcchavezs/porto/cmd/porto MULTIMOD = $(TOOLS)/multimod $(MULTIMOD): PACKAGE=go.opentelemetry.io/build-tools/multimod CROSSLINK = $(TOOLS)/crosslink $(CROSSLINK): PACKAGE=go.opentelemetry.io/build-tools/crosslink GOJQ = $(TOOLS)/gojq $(TOOLS)/gojq: PACKAGE=github.com/itchyny/gojq/cmd/gojq GOTMPL = $(TOOLS)/gotmpl $(GOTMPL): PACKAGE=go.opentelemetry.io/build-tools/gotmpl GORELEASE = $(TOOLS)/gorelease $(GORELEASE): PACKAGE=golang.org/x/exp/cmd/gorelease GOJSONSCHEMA = $(TOOLS)/go-jsonschema $(GOJSONSCHEMA): PACKAGE=github.com/atombender/go-jsonschema GOVULNCHECK = $(TOOLS)/govulncheck $(GOVULNCHECK): PACKAGE=golang.org/x/vuln/cmd/govulncheck tools: $(GOLANGCI_LINT) $(MISSPELL) $(GOCOVMERGE) $(STRINGER) $(PORTO) $(GOJQ) $(MULTIMOD) $(CROSSLINK) $(GOTMPL) $(GORELEASE) $(GOJSONSCHEMA) $(GOVULNCHECK) # Virtualized python tools via docker # The directory where the virtual environment is created. VENVDIR := venv # The directory where the python tools are installed. PYTOOLS := $(VENVDIR)/bin # The pip executable in the virtual environment. PIP := $(PYTOOLS)/pip # The directory in the docker image where the current directory is mounted. WORKDIR := /workdir # The python image to use for the virtual environment. PYTHONIMAGE := python:3.11.3-slim-bullseye # Run the python image with the current directory mounted. DOCKERPY := docker run --rm -v "$(CURDIR):$(WORKDIR)" -w $(WORKDIR) $(PYTHONIMAGE) # Create a virtual environment for Python tools. $(PYTOOLS): # The `--upgrade` flag is needed to ensure that the virtual environment is # created with the latest pip version. @$(DOCKERPY) bash -c "python3 -m venv $(VENVDIR) && $(PIP) install --upgrade pip" # Install python packages into the virtual environment. $(PYTOOLS)/%: $(PYTOOLS) @$(DOCKERPY) $(PIP) install -r requirements.txt CODESPELL = $(PYTOOLS)/codespell $(CODESPELL): PACKAGE=codespell # Generate .PHONY: generate generate: go-generate vanity-import-fix .PHONY: go-generate go-generate: $(OTEL_GO_MOD_DIRS:%=go-generate/%) go-generate/%: DIR=$* go-generate/%: $(STRINGER) $(GOTMPL) @echo "$(GO) generate $(DIR)/..." \ && cd $(DIR) \ && PATH="$(TOOLS):$${PATH}" $(GO) generate ./... .PHONY: vanity-import-fix vanity-import-fix: $(PORTO) @$(PORTO) --include-internal -w . # Generate go.work file for local development. .PHONY: go-work go-work: $(CROSSLINK) $(CROSSLINK) work --root=$(shell pwd) # Build .PHONY: build build: $(OTEL_GO_MOD_DIRS:%=build/%) $(OTEL_GO_MOD_DIRS:%=build-tests/%) build/%: DIR=$* build/%: @echo "$(GO) build $(DIR)/..." \ && cd $(DIR) \ && $(GO) build ./... build-tests/%: DIR=$* build-tests/%: @echo "$(GO) build tests $(DIR)/..." \ && cd $(DIR) \ && $(GO) list ./... \ | grep -v third_party \ | xargs $(GO) test -vet=off -run xxxxxMatchNothingxxxxx >/dev/null # Linting .PHONY: golangci-lint golangci-lint-fix golangci-lint-fix: ARGS=--fix golangci-lint-fix: golangci-lint golangci-lint: $(OTEL_GO_MOD_DIRS:%=golangci-lint/%) golangci-lint/%: DIR=$* golangci-lint/%: $(GOLANGCI_LINT) @echo 'golangci-lint $(if $(ARGS),$(ARGS) ,)$(DIR)' \ && cd $(DIR) \ && $(GOLANGCI_LINT) run --allow-serial-runners $(ARGS) .PHONY: crosslink crosslink: $(CROSSLINK) @echo "Updating intra-repository dependencies in all go modules" \ && $(CROSSLINK) --root=$(shell pwd) --prune .PHONY: go-mod-tidy go-mod-tidy: $(ALL_GO_MOD_DIRS:%=go-mod-tidy/%) go-mod-tidy/%: DIR=$* go-mod-tidy/%: @echo "$(GO) mod tidy in $(DIR)" \ && cd $(DIR) \ && $(GO) mod tidy -compat=1.21 .PHONY: misspell misspell: $(MISSPELL) @$(MISSPELL) -w $(ALL_DOCS) .PHONY: govulncheck govulncheck: $(ALL_GO_MOD_DIRS:%=govulncheck/%) govulncheck/%: DIR=$* govulncheck/%: $(GOVULNCHECK) @echo "govulncheck in $(DIR)" \ && cd $(DIR) \ && $(GOVULNCHECK) ./... .PHONY: vanity-import-check vanity-import-check: | $(PORTO) @$(PORTO) --include-internal -l . || ( echo "(run: make vanity-import-fix)"; exit 1 ) .PHONY: lint lint: go-mod-tidy golangci-lint misspell govulncheck .PHONY: license-check license-check: @licRes=$$(for f in $$(find . -type f \( -iname '*.go' -o -iname '*.sh' \) ! -path './vendor/*' ! -path './exporters/otlp/internal/opentelemetry-proto/*') ; do \ awk '/Copyright The OpenTelemetry Authors|generated|GENERATED/ && NR<=4 { found=1; next } END { if (!found) print FILENAME }' $$f; \ done); \ if [ -n "$${licRes}" ]; then \ echo "license header checking failed:"; echo "$${licRes}"; \ exit 1; \ fi .PHONY: registry-links-check registry-links-check: @checkRes=$$( \ for f in $$( find ./instrumentation ./exporters ./detectors ! -path './instrumentation/net/*' -type f -name 'go.mod' -exec dirname {} \; | egrep -v '/example|/utils' | sort ) \ ./instrumentation/net/http; do \ TYPE="instrumentation"; \ if $$(echo "$$f" | grep -q "exporters"); then \ TYPE="exporter"; \ fi; \ if $$(echo "$$f" | grep -q "detectors"); then \ TYPE="detector"; \ fi; \ NAME=$$(echo "$$f" | sed -e 's/.*\///' -e 's/.*otel//'); \ LINK=$(CONTRIB_REPO_URL)/$$(echo "$$f" | sed -e 's/..//' -e 's/\/otel.*$$//'); \ if ! $$(curl -s $(REGISTRY_BASE_URL)/$${TYPE}-go-$${NAME}.md | grep -q "$${LINK}"); then \ echo "$$f"; \ fi \ done; \ ); \ if [ -n "$$checkRes" ]; then \ echo "WARNING: registry link check failed for the following packages:"; echo "$${checkRes}"; \ fi .PHONY: check-clean-work-tree check-clean-work-tree: @if ! git diff --quiet; then \ echo; \ echo 'Working tree is not clean, did you forget to run "make precommit"?'; \ echo; \ git status; \ exit 1; \ fi # Tests TEST_TARGETS := test-default test-bench test-short test-verbose test-race .PHONY: $(TEST_TARGETS) test test-default test-race: ARGS=-race test-bench: ARGS=-run=xxxxxMatchNothingxxxxx -test.benchtime=1ms -bench=. test-short: ARGS=-short test-verbose: ARGS=-v $(TEST_TARGETS): test test: $(OTEL_GO_MOD_DIRS:%=test/%) test/%: DIR=$* test/%: @echo "$(GO) test -timeout $(TIMEOUT)s $(ARGS) $(DIR)/..." \ && cd $(DIR) \ && $(GO) test -timeout $(TIMEOUT)s $(ARGS) ./... COVERAGE_MODE = atomic COVERAGE_PROFILE = coverage.out .PHONY: test-coverage test-coverage: $(ALL_COVERAGE_MOD_DIRS:%=test-coverage/%) | $(GOCOVMERGE) @printf "" > coverage.txt \ && $(GOCOVMERGE) $$(find . -name $(COVERAGE_PROFILE)) > coverage.txt test-coverage/%: DIR=$* test-coverage/%: @set -e; \ CMD="$(GO) test -race -covermode=$(COVERAGE_MODE) -coverprofile=$(COVERAGE_PROFILE)"; \ echo "$(DIR)" | grep -q 'test$$' \ && CMD="$$CMD -coverpkg=go.opentelemetry.io/contrib/$$( dirname "$(DIR)" | sed -e "s/^\.\///g" )/..."; \ echo "$$CMD $(DIR)/..."; \ cd "$(DIR)" \ && $$CMD ./... \ && $(GO) tool cover -html=coverage.out -o coverage.html; # Releasing .PHONY: gorelease gorelease: $(OTEL_GO_MOD_DIRS:%=gorelease/%) gorelease/%: DIR=$* gorelease/%: $(GORELEASE) @echo "gorelease in $(DIR):" \ && cd $(DIR) \ && $(GORELEASE) \ || echo "" COREPATH ?= "../opentelemetry-go" .PHONY: sync-core sync-core: $(MULTIMOD) @[ ! -d $COREPATH ] || ( echo ">> Path to core repository must be set in COREPATH and must exist"; exit 1 ) $(MULTIMOD) verify && $(MULTIMOD) sync -a -o ${COREPATH} .PHONY: prerelease prerelease: $(MULTIMOD) @[ "${MODSET}" ] || ( echo ">> env var MODSET is not set"; exit 1 ) $(MULTIMOD) verify && $(MULTIMOD) prerelease -m ${MODSET} COMMIT ?= "HEAD" .PHONY: add-tags add-tags: $(MULTIMOD) @[ "${MODSET}" ] || ( echo ">> env var MODSET is not set"; exit 1 ) $(MULTIMOD) verify && $(MULTIMOD) tag -m ${MODSET} -c ${COMMIT} .PHONY: update-all-otel-deps update-all-otel-deps: @[ "${GITSHA}" ] || ( echo ">> env var GITSHA is not set"; exit 1 ) @echo "Updating OpenTelemetry dependencies to ${GITSHA}" @set -e; \ for dir in $(OTEL_GO_MOD_DIRS); do \ echo "Updating OpenTelemetry dependencies in $${dir}"; \ (cd $${dir} \ && grep -o 'go.opentelemetry.io/otel\S*' go.mod | xargs -I {} -n1 $(GO) get {}@${GITSHA}); \ done # The source directory for opentelemetry-configuration schema. OPENTELEMETRY_CONFIGURATION_JSONSCHEMA_SRC_DIR=tmp/opentelememetry-configuration # The SHA matching the current version of the opentelemetry-configuration schema to use OPENTELEMETRY_CONFIGURATION_JSONSCHEMA_VERSION=v0.3.0 # Cleanup temporary directory genjsonschema-cleanup: rm -Rf ${OPENTELEMETRY_CONFIGURATION_JSONSCHEMA_SRC_DIR} GENERATED_CONFIG=./config/generated_config.go # Generate structs for configuration from opentelemetry-configuration schema genjsonschema: genjsonschema-cleanup $(GOJSONSCHEMA) mkdir -p ${OPENTELEMETRY_CONFIGURATION_JSONSCHEMA_SRC_DIR} curl -sSL https://api.github.com/repos/open-telemetry/opentelemetry-configuration/tarball/${OPENTELEMETRY_CONFIGURATION_JSONSCHEMA_VERSION} | tar xz --strip 1 -C ${OPENTELEMETRY_CONFIGURATION_JSONSCHEMA_SRC_DIR} $(GOJSONSCHEMA) \ --capitalization ID \ --capitalization OTLP \ --struct-name-from-title \ --package config \ --only-models \ --output ${GENERATED_CONFIG} \ ${OPENTELEMETRY_CONFIGURATION_JSONSCHEMA_SRC_DIR}/schema/opentelemetry_configuration.json @echo Modify jsonschema generated files. sed -f ./config/jsonschema_patch.sed ${GENERATED_CONFIG} > ${GENERATED_CONFIG}.tmp mv ${GENERATED_CONFIG}.tmp ${GENERATED_CONFIG} $(MAKE) lint $(MAKE) genjsonschema-cleanup .PHONY: codespell codespell: $(CODESPELL) @$(DOCKERPY) $(CODESPELL) open-telemetry-opentelemetry-go-contrib-e5abccb/README.md000066400000000000000000000101711470323427300235300ustar00rootroot00000000000000# OpenTelemetry-Go Contrib [![build_and_test](https://github.com/open-telemetry/opentelemetry-go-contrib/workflows/build_and_test/badge.svg)](https://github.com/open-telemetry/opentelemetry-go-contrib/actions?query=workflow%3Abuild_and_test+branch%3Amain) [![codecov.io](https://codecov.io/gh/open-telemetry/opentelemetry-go-contrib/coverage.svg?branch=main)](https://app.codecov.io/gh/open-telemetry/opentelemetry-go-contrib?branch=main) [![Docs](https://godoc.org/go.opentelemetry.io/contrib?status.svg)](https://pkg.go.dev/go.opentelemetry.io/contrib) [![Go Report Card](https://goreportcard.com/badge/go.opentelemetry.io/contrib)](https://goreportcard.com/report/go.opentelemetry.io/contrib) [![Slack](https://img.shields.io/badge/slack-@cncf/otel--go-brightgreen.svg?logo=slack)](https://cloud-native.slack.com/archives/C01NPAXACKT) Collection of 3rd-party packages for [OpenTelemetry-Go](https://github.com/open-telemetry/opentelemetry-go). ## Contents - [Examples](./examples/): Examples of OpenTelemetry libraries usage. - [Instrumentation](./instrumentation/): Packages providing OpenTelemetry instrumentation for 3rd-party libraries. - [Propagators](./propagators/): Packages providing OpenTelemetry context propagators for 3rd-party propagation formats. - [Detectors](./detectors/): Packages providing OpenTelemetry resource detectors for 3rd-party cloud computing environments. - [Exporters](./exporters/): Packages providing OpenTelemetry exporters for 3rd-party export formats. - [Samplers](./samplers/): Packages providing additional implementations of OpenTelemetry samplers. - [Bridges](./bridges/): Packages providing adapters for 3rd-party instrumentation frameworks. - [Processors](./processors/): Packages providing additional implementations of OpenTelemetry processors. ## Project Status This project contains both stable and unstable modules. Refer to the module for its version or our [versioning manifest](./versions.yaml). Project versioning information and stability guarantees can be found in the [versioning documentation](https://github.com/open-telemetry/opentelemetry-go/blob/a724cf884287e04785eaa91513d26a6ef9699288/VERSIONING.md). Progress and status specific to this repository is tracked in our local [project boards](https://github.com/open-telemetry/opentelemetry-go-contrib/projects?query=is%3Aopen) and [milestones](https://github.com/open-telemetry/opentelemetry-go-contrib/milestones). ### Compatibility OpenTelemetry-Go Contrib ensures compatibility with the current supported versions of the [Go language](https://golang.org/doc/devel/release#policy): > Each major Go release is supported until there are two newer major releases. > For example, Go 1.5 was supported until the Go 1.7 release, and Go 1.6 was supported until the Go 1.8 release. For versions of Go that are no longer supported upstream, opentelemetry-go-contrib will stop ensuring compatibility with these versions in the following manner: - A minor release of opentelemetry-go-contrib will be made to add support for the new supported release of Go. - The following minor release of opentelemetry-go-contrib will remove compatibility testing for the oldest (now archived upstream) version of Go. This, and future, releases of opentelemetry-go-contrib may include features only supported by the currently supported versions of Go. This project is tested on the following systems. | OS | Go Version | Architecture | | -------- | ---------- | ------------ | | Ubuntu | 1.23 | amd64 | | Ubuntu | 1.22 | amd64 | | Ubuntu | 1.23 | 386 | | Ubuntu | 1.22 | 386 | | macOS 13 | 1.23 | amd64 | | macOS 13 | 1.22 | amd64 | | macOS | 1.23 | arm64 | | macOS | 1.22 | arm64 | | Windows | 1.23 | amd64 | | Windows | 1.22 | amd64 | | Windows | 1.23 | 386 | | Windows | 1.22 | 386 | While this project should work for other systems, no compatibility guarantees are made for those systems currently. ## Contributing For information on how to contribute, consult [the contributing guidelines](./CONTRIBUTING.md) open-telemetry-opentelemetry-go-contrib-e5abccb/RELEASING.md000066400000000000000000000130271470323427300241070ustar00rootroot00000000000000# Release Process This project uses the [`multimod` releaser tool](https://github.com/open-telemetry/opentelemetry-go-build-tools/tree/main/multimod) to manage releases. This document will walk you through how to perform a release using this tool for this repository. ## Before releasing ### Verify OTel changes Before releasing, it is important to verify that the changes in the upstream go.opentelemetry.io/otel packages are compatible with the contrib repository. Follow the following steps to verify the changes. 1. Pick the GIT SHA on the [main branch](https://github.com/open-telemetry/opentelemetry-go/commits/main) that you want to verify. 2. Run the following command to update the OTel dependencies with the GIT SHA picked in step 1. ```sh export GITSHA= make update-all-otel-deps make go-mod-tidy ``` 3. Verify the changes. ```sh git diff ``` This should have changed the version for all OTel modules to be the GIT SHA picked in step 1. 4. Run the lint and tests to verify that the changes are compatible with the contrib repository. ```sh make precommit ``` This command should be passed without any errors. ## Start a release First, decide which module sets will have their versions changed and what those versions will be. If you are making a release to upgrade the upstream go.opentelemetry.io/otel packages, all module sets will likely need to be released. ### Breaking changes validation You can run `make gorelease` that runs [gorelease](https://pkg.go.dev/golang.org/x/exp/cmd/gorelease) to ensure that there are no unwanted changes done in the public API. You can check/report problems with `gorelease` [here](https://golang.org/issues/26420). ### Create a release branch Update the versions of the module sets you have identified in `versions.yaml`. Commit this change to a new release branch. ### Upgrade go.opentelemetry.io/otel packages If the upstream go.opentelemetry.io/otel project has made a release, this project needs to be upgraded to use that release. ```sh make sync-core COREPATH= ``` This will use `multimod` to upgrade all go.opentelemetry.io/otel packages to the latest tag found in the local copy of the project. Be sure to have this project up to date. Commit these changes to your release branch. ### Update module set versions Set the version for all the module sets you have identified to be released. ```sh make prerelease MODSET= ``` This will use `multimod` to upgrade the module's versions and create a new "prerelease" branch for the changes. Verify the changes that were made. ```sh git diff HEAD..prerelease__ ``` Fix any issues if they exist in that prerelease branch, and when ready, merge it into your release branch. ```sh git merge prerelease__ ``` ### Update the CHANGELOG.md Update the [Changelog](./CHANGELOG.md). Make sure only changes relevant to this release are included and the changes are communicated in language that non-contributors to the project can understand. Double check there is no change missing by looking directly at the commits since the last release tag. ```sh git --no-pager log --pretty=oneline "..HEAD" ``` Make sure the new released section is under the comment for released section, like ``, so it is protected from being overwritten in the future. Be sure to update all the appropriate links at the bottom of the file. Finally, commit this change to your release branch. ### Make a Pull Request Push your release branch and create a pull request for the changes. Be sure to include the curated changes your included in the changelog in the description. Especially include the change PR references, as this will help show viewers of the repository looking at these PRs that they are included in the release. ## Tag a release Once the Pull Request with all the version changes has been approved and merged it is time to tag the merged commit. ***IMPORTANT***: It is critical you use the same tag that you used in the Pre-Release step! Failure to do so will leave things in a broken state. As long as you do not change `versions.yaml` between pre-release and this step, things should be fine. 1. For each module set that will be released, run the `add-tags` make target using the `` of the commit on the main branch for the merged Pull Request. ```sh make add-tags MODSET= COMMIT= ``` It should only be necessary to provide an explicit `COMMIT` value if the current `HEAD` of your working directory is not the correct commit. 2. Push tags to the upstream remote (not your fork: `github.com/open-telemetry/opentelemetry-go-contrib.git`). Make sure you push all sub-modules as well. ```sh export VERSION="" for t in $( git tag -l | grep "$VERSION" ); do git push upstream "$t"; done ``` ## Release Finally create a Release on GitHub. If you are release multiple versions for different module sets, be sure to use the stable release tag but be sure to include each version in the release title (i.e. `Release v1.0.0/v0.25.0`). The release body should include all the curated changes from the Changelog for this release. ## Verify Examples After releasing verify that examples build outside of the repository. ```sh ./verify_examples.sh ``` The script copies examples into a different directory removes any `replace` declarations in `go.mod` and builds them. This ensures they build with the published release, not the local copy. open-telemetry-opentelemetry-go-contrib-e5abccb/bridges/000077500000000000000000000000001470323427300236705ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/bridges/otellogr/000077500000000000000000000000001470323427300255175ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/bridges/otellogr/convert.go000066400000000000000000000077551470323427300275440ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otellogr // import "go.opentelemetry.io/contrib/bridges/otellogr" import ( "context" "fmt" "math" "reflect" "strconv" "time" "go.opentelemetry.io/otel/log" ) // convertKVs converts a list of key-value pairs to a list of [log.KeyValue]. // The last [context.Context] value is returned as the context. // If no context is found, the original context is returned. func convertKVs(ctx context.Context, keysAndValues ...any) (context.Context, []log.KeyValue) { if len(keysAndValues) == 0 { return ctx, nil } if len(keysAndValues)%2 != 0 { // Ensure an odd number of items here does not corrupt the list. keysAndValues = append(keysAndValues, nil) } kvs := make([]log.KeyValue, 0, len(keysAndValues)/2) for i := 0; i < len(keysAndValues); i += 2 { k, ok := keysAndValues[i].(string) if !ok { // Ensure that the key is a string. k = fmt.Sprintf("%v", keysAndValues[i]) } v := keysAndValues[i+1] if vCtx, ok := v.(context.Context); ok { // Special case when a field is of context.Context type. ctx = vCtx continue } kvs = append(kvs, log.KeyValue{ Key: k, Value: convertValue(v), }) } return ctx, kvs } func convertValue(v any) log.Value { // Handling the most common types without reflect is a small perf win. switch val := v.(type) { case bool: return log.BoolValue(val) case string: return log.StringValue(val) case int: return log.Int64Value(int64(val)) case int8: return log.Int64Value(int64(val)) case int16: return log.Int64Value(int64(val)) case int32: return log.Int64Value(int64(val)) case int64: return log.Int64Value(val) case uint: return convertUintValue(uint64(val)) case uint8: return log.Int64Value(int64(val)) case uint16: return log.Int64Value(int64(val)) case uint32: return log.Int64Value(int64(val)) case uint64: return convertUintValue(val) case uintptr: return convertUintValue(uint64(val)) case float32: return log.Float64Value(float64(val)) case float64: return log.Float64Value(val) case time.Duration: return log.Int64Value(val.Nanoseconds()) case complex64: r := log.Float64("r", real(complex128(val))) i := log.Float64("i", imag(complex128(val))) return log.MapValue(r, i) case complex128: r := log.Float64("r", real(val)) i := log.Float64("i", imag(val)) return log.MapValue(r, i) case time.Time: return log.Int64Value(val.UnixNano()) case []byte: return log.BytesValue(val) case error: return log.StringValue(val.Error()) } t := reflect.TypeOf(v) if t == nil { return log.Value{} } val := reflect.ValueOf(v) switch t.Kind() { case reflect.Struct: return log.StringValue(fmt.Sprintf("%+v", v)) case reflect.Slice, reflect.Array: items := make([]log.Value, 0, val.Len()) for i := 0; i < val.Len(); i++ { items = append(items, convertValue(val.Index(i).Interface())) } return log.SliceValue(items...) case reflect.Map: kvs := make([]log.KeyValue, 0, val.Len()) for _, k := range val.MapKeys() { var key string switch k.Kind() { case reflect.String: key = k.String() default: key = fmt.Sprintf("%+v", k.Interface()) } kvs = append(kvs, log.KeyValue{ Key: key, Value: convertValue(val.MapIndex(k).Interface()), }) } return log.MapValue(kvs...) case reflect.Ptr, reflect.Interface: if val.IsNil() { return log.Value{} } return convertValue(val.Elem().Interface()) } // Try to handle this as gracefully as possible. // // Don't panic here. it is preferable to have user's open issue // asking why their attributes have a "unhandled: " prefix than // say that their code is panicking. return log.StringValue(fmt.Sprintf("unhandled: (%s) %+v", t, v)) } // convertUintValue converts a uint64 to a log.Value. // If the value is too large to fit in an int64, it is converted to a string. func convertUintValue(v uint64) log.Value { if v > math.MaxInt64 { return log.StringValue(strconv.FormatUint(v, 10)) } return log.Int64Value(int64(v)) } open-telemetry-opentelemetry-go-contrib-e5abccb/bridges/otellogr/convert_test.go000066400000000000000000000150071470323427300305700ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otellogr import ( "context" "errors" "fmt" "testing" "time" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/log" ) func TestConvertKVs(t *testing.T) { ctx := context.WithValue(context.Background(), "key", "value") // nolint: revive,staticcheck // test context for _, tt := range []struct { name string kvs []any wantKVs []log.KeyValue wantCtx context.Context }{ { name: "empty", kvs: []any{}, }, { name: "single_value", kvs: []any{"key", "value"}, wantKVs: []log.KeyValue{ log.String("key", "value"), }, }, { name: "multiple_values", kvs: []any{"key1", "value1", "key2", "value2"}, wantKVs: []log.KeyValue{ log.String("key1", "value1"), log.String("key2", "value2"), }, }, { name: "missing_value", kvs: []any{"key1", "value1", "key2"}, wantKVs: []log.KeyValue{ log.String("key1", "value1"), {Key: "key2", Value: log.Value{}}, }, }, { name: "key_not_string", kvs: []any{42, "value"}, wantKVs: []log.KeyValue{ log.String("42", "value"), }, }, { name: "context", kvs: []any{"ctx", ctx, "key", "value"}, wantKVs: []log.KeyValue{log.String("key", "value")}, wantCtx: ctx, }, { name: "last_context", kvs: []any{"key", context.Background(), "ctx", ctx}, wantKVs: []log.KeyValue{}, wantCtx: ctx, }, } { t.Run(tt.name, func(t *testing.T) { ctx, kvs := convertKVs(nil, tt.kvs...) // nolint: staticcheck // pass nil context assert.Equal(t, tt.wantKVs, kvs) assert.Equal(t, tt.wantCtx, ctx) }) } } func TestConvertValue(t *testing.T) { for _, tt := range []struct { name string value any wantValue log.Value }{ { name: "bool", value: true, wantValue: log.BoolValue(true), }, { name: "string", value: "value", wantValue: log.StringValue("value"), }, { name: "int", value: 10, wantValue: log.Int64Value(10), }, { name: "int8", value: int8(127), wantValue: log.Int64Value(127), }, { name: "int16", value: int16(32767), wantValue: log.Int64Value(32767), }, { name: "int32", value: int32(2147483647), wantValue: log.Int64Value(2147483647), }, { name: "int64", value: int64(9223372036854775807), wantValue: log.Int64Value(9223372036854775807), }, { name: "uint", value: uint(42), wantValue: log.Int64Value(42), }, { name: "uint8", value: uint8(255), wantValue: log.Int64Value(255), }, { name: "uint16", value: uint16(65535), wantValue: log.Int64Value(65535), }, { name: "uint32", value: uint32(4294967295), wantValue: log.Int64Value(4294967295), }, { name: "uint64", value: uint64(9223372036854775807), wantValue: log.Int64Value(9223372036854775807), }, { name: "uint64-max", value: uint64(18446744073709551615), wantValue: log.StringValue("18446744073709551615"), }, { name: "uintptr", value: uintptr(12345), wantValue: log.Int64Value(12345), }, { name: "float64", value: float64(3.14159), wantValue: log.Float64Value(3.14159), }, { name: "time.Duration", value: time.Second, wantValue: log.Int64Value(1_000_000_000), }, { name: "complex64", value: complex64(complex(float32(1), float32(2))), wantValue: log.MapValue(log.Float64("r", 1), log.Float64("i", 2)), }, { name: "complex128", value: complex(float64(3), float64(4)), wantValue: log.MapValue(log.Float64("r", 3), log.Float64("i", 4)), }, { name: "time.Time", value: time.Unix(1000, 1000), wantValue: log.Int64Value(time.Unix(1000, 1000).UnixNano()), }, { name: "[]byte", value: []byte("hello"), wantValue: log.BytesValue([]byte("hello")), }, { name: "error", value: errors.New("test error"), wantValue: log.StringValue("test error"), }, { name: "error", value: errors.New("test error"), wantValue: log.StringValue("test error"), }, { name: "error-nested", value: fmt.Errorf("test error: %w", errors.New("nested error")), wantValue: log.StringValue("test error: nested error"), }, { name: "nil", value: nil, wantValue: log.Value{}, }, { name: "nil_ptr", value: (*int)(nil), wantValue: log.Value{}, }, { name: "int_ptr", value: func() *int { i := 93; return &i }(), wantValue: log.Int64Value(93), }, { name: "string_ptr", value: func() *string { s := "hello"; return &s }(), wantValue: log.StringValue("hello"), }, { name: "bool_ptr", value: func() *bool { b := true; return &b }(), wantValue: log.BoolValue(true), }, { name: "int_empty_array", value: []int{}, wantValue: log.SliceValue([]log.Value{}...), }, { name: "int_array", value: []int{1, 2, 3}, wantValue: log.SliceValue([]log.Value{ log.Int64Value(1), log.Int64Value(2), log.Int64Value(3), }...), }, { name: "key_value_map", value: map[string]int{"one": 1}, wantValue: log.MapValue( log.Int64("one", 1), ), }, { name: "int_string_map", value: map[int]string{1: "one"}, wantValue: log.MapValue( log.String("1", "one"), ), }, { name: "nested_map", value: map[string]map[string]int{"nested": {"one": 1}}, wantValue: log.MapValue( log.Map("nested", log.Int64("one", 1), ), ), }, { name: "struct_key_map", value: map[struct{ Name string }]int{ {Name: "John"}: 42, }, wantValue: log.MapValue( log.Int64("{Name:John}", 42), ), }, { name: "struct", value: struct { Name string Age int }{ Name: "John", Age: 42, }, wantValue: log.StringValue("{Name:John Age:42}"), }, { name: "struct_ptr", value: &struct { Name string Age int }{ Name: "John", Age: 42, }, wantValue: log.StringValue("{Name:John Age:42}"), }, { name: "ctx", value: context.Background(), wantValue: log.StringValue("context.Background"), }, } { t.Run(tt.name, func(t *testing.T) { assert.Equal(t, tt.wantValue, convertValue(tt.value)) }) } } func TestConvertValueFloat32(t *testing.T) { value := convertValue(float32(3.14)) want := log.Float64Value(3.14) assert.InDelta(t, value.AsFloat64(), want.AsFloat64(), 0.0001) } open-telemetry-opentelemetry-go-contrib-e5abccb/bridges/otellogr/example_test.go000066400000000000000000000010601470323427300305350ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otellogr_test import ( "github.com/go-logr/logr" "go.opentelemetry.io/contrib/bridges/otellogr" "go.opentelemetry.io/otel/log/noop" ) func Example() { // Use a working LoggerProvider implementation instead e.g. using go.opentelemetry.io/otel/sdk/log. provider := noop.NewLoggerProvider() // Create an logr.Logger with *otellogr.LogSink and use it in your application. logr.New(otellogr.NewLogSink( "my/pkg/name", otellogr.WithLoggerProvider(provider)), ) } open-telemetry-opentelemetry-go-contrib-e5abccb/bridges/otellogr/go.mod000066400000000000000000000010111470323427300266160ustar00rootroot00000000000000module go.opentelemetry.io/contrib/bridges/otellogr go 1.22 require ( github.com/go-logr/logr v1.4.2 github.com/stretchr/testify v1.9.0 go.opentelemetry.io/otel/log v0.7.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/otel v1.31.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect go.opentelemetry.io/otel/trace v1.31.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) open-telemetry-opentelemetry-go-contrib-e5abccb/bridges/otellogr/go.sum000066400000000000000000000041741470323427300266600ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/log v0.7.0 h1:d1abJc0b1QQZADKvfe9JqqrfmPYQCz2tUSO+0XZmuV4= go.opentelemetry.io/otel/log v0.7.0/go.mod h1:2jf2z7uVfnzDNknKTO9G+ahcOAyWcp1fJmk/wJjULRo= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= open-telemetry-opentelemetry-go-contrib-e5abccb/bridges/otellogr/logsink.go000066400000000000000000000143141470323427300275170ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package otellogr provides a [LogSink], a [logr.LogSink] implementation that // can be used to bridge between the [logr] API and [OpenTelemetry]. // // # Record Conversion // // The logr records are converted to OpenTelemetry [log.Record] in the following // way: // // - Message is set as the Body using a [log.StringValue]. // - TODO: Level // - KeyAndValues are transformed and set as Attributes. // - The [context.Context] value in KeyAndValues is propagated to OpenTelemetry // log record. All non-nested [context.Context] values are ignored and not // added as attributes. If there are multiple [context.Context] the last one // is used. // // KeysAndValues values are transformed based on their type. The following types are // supported: // // - [bool] are transformed to [log.BoolValue]. // - [string] are transformed to [log.StringValue]. // - [int], [int8], [int16], [int32], [int64] are transformed to // [log.Int64Value]. // - [uint], [uint8], [uint16], [uint32], [uint64], [uintptr] are transformed // to [log.Int64Value] or [log.StringValue] if the value is too large. // - [float32], [float64] are transformed to [log.Float64Value]. // - [time.Duration] are transformed to [log.Int64Value] with the nanoseconds. // - [complex64], [complex128] are transformed to [log.MapValue] with the keys // "r" and "i" for the real and imaginary parts. The values are // [log.Float64Value]. // - [time.Time] are transformed to [log.Int64Value] with the nanoseconds. // - [[]byte] are transformed to [log.BytesValue]. // - [error] are transformed to [log.StringValue] with the error message. // - [nil] are transformed to an empty [log.Value]. // - [struct] are transformed to [log.StringValue] with the struct fields. // - [slice], [array] are transformed to [log.SliceValue] with the elements. // - [map] are transformed to [log.MapValue] with the key-value pairs. // - [pointer], [interface] are transformed to the dereferenced value. // // [OpenTelemetry]: https://opentelemetry.io/docs/concepts/signals/logs/ package otellogr // import "go.opentelemetry.io/contrib/bridges/otellogr" import ( "context" "github.com/go-logr/logr" "go.opentelemetry.io/otel/log" "go.opentelemetry.io/otel/log/global" ) type config struct { provider log.LoggerProvider version string schemaURL string } func newConfig(options []Option) config { var c config for _, opt := range options { c = opt.apply(c) } if c.provider == nil { c.provider = global.GetLoggerProvider() } return c } // Option configures a [LogSink]. type Option interface { apply(config) config } type optFunc func(config) config func (f optFunc) apply(c config) config { return f(c) } // WithVersion returns an [Option] that configures the version of the // [log.Logger] used by a [LogSink]. The version should be the version of the // package that is being logged. func WithVersion(version string) Option { return optFunc(func(c config) config { c.version = version return c }) } // WithSchemaURL returns an [Option] that configures the semantic convention // schema URL of the [log.Logger] used by a [LogSink]. The schemaURL should be // the schema URL for the semantic conventions used in log records. func WithSchemaURL(schemaURL string) Option { return optFunc(func(c config) config { c.schemaURL = schemaURL return c }) } // WithLoggerProvider returns an [Option] that configures [log.LoggerProvider] // used by a [LogSink] to create its [log.Logger]. // // By default if this Option is not provided, the LogSink will use the global // LoggerProvider. func WithLoggerProvider(provider log.LoggerProvider) Option { return optFunc(func(c config) config { c.provider = provider return c }) } // NewLogSink returns a new [LogSink] to be used as a [logr.LogSink]. // // If [WithLoggerProvider] is not provided, the returned [LogSink] will use the // global LoggerProvider. func NewLogSink(name string, options ...Option) *LogSink { c := newConfig(options) var opts []log.LoggerOption if c.version != "" { opts = append(opts, log.WithInstrumentationVersion(c.version)) } if c.schemaURL != "" { opts = append(opts, log.WithSchemaURL(c.schemaURL)) } return &LogSink{ name: name, provider: c.provider, logger: c.provider.Logger(name, opts...), opts: opts, } } // LogSink is a [logr.LogSink] that sends all logging records it receives to // OpenTelemetry. See package documentation for how conversions are made. type LogSink struct { // Ensure forward compatibility by explicitly making this not comparable. noCmp [0]func() //nolint: unused // This is indeed used. name string provider log.LoggerProvider logger log.Logger opts []log.LoggerOption attr []log.KeyValue ctx context.Context } // Compile-time check *Handler implements logr.LogSink. var _ logr.LogSink = (*LogSink)(nil) // Enabled tests whether this LogSink is enabled at the specified V-level. // For example, commandline flags might be used to set the logging // verbosity and disable some info logs. func (l *LogSink) Enabled(level int) bool { // TODO return true } // Error logs an error, with the given message and key/value pairs. func (l *LogSink) Error(err error, msg string, keysAndValues ...any) { // TODO } // Info logs a non-error message with the given key/value pairs. func (l *LogSink) Info(level int, msg string, keysAndValues ...any) { var record log.Record record.SetBody(log.StringValue(msg)) record.SetSeverity(log.SeverityInfo) // TODO: level record.AddAttributes(l.attr...) ctx, attr := convertKVs(l.ctx, keysAndValues...) record.AddAttributes(attr...) l.logger.Emit(ctx, record) } // Init initializes the LogSink. func (l *LogSink) Init(info logr.RuntimeInfo) { // TODO } // WithName returns a new LogSink with the specified name appended. func (l LogSink) WithName(name string) logr.LogSink { l.name = l.name + "/" + name l.logger = l.provider.Logger(l.name, l.opts...) return &l } // WithValues returns a new LogSink with additional key/value pairs. func (l LogSink) WithValues(keysAndValues ...any) logr.LogSink { ctx, attr := convertKVs(l.ctx, keysAndValues...) l.attr = append(l.attr, attr...) l.ctx = ctx return &l } open-telemetry-opentelemetry-go-contrib-e5abccb/bridges/otellogr/logsink_test.go000066400000000000000000000136621470323427300305630ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otellogr import ( "testing" "time" "github.com/go-logr/logr" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/log" "go.opentelemetry.io/otel/log/embedded" "go.opentelemetry.io/otel/log/global" "go.opentelemetry.io/otel/log/logtest" ) type mockLoggerProvider struct { embedded.LoggerProvider } func (mockLoggerProvider) Logger(name string, options ...log.LoggerOption) log.Logger { return nil } func TestNewConfig(t *testing.T) { customLoggerProvider := mockLoggerProvider{} for _, tt := range []struct { name string options []Option wantConfig config }{ { name: "with no options", wantConfig: config{ provider: global.GetLoggerProvider(), }, }, { name: "with a custom instrumentation scope", options: []Option{ WithVersion("42.0"), }, wantConfig: config{ version: "42.0", provider: global.GetLoggerProvider(), }, }, { name: "with a custom logger provider", options: []Option{ WithLoggerProvider(customLoggerProvider), }, wantConfig: config{ provider: customLoggerProvider, }, }, } { t.Run(tt.name, func(t *testing.T) { assert.Equal(t, tt.wantConfig, newConfig(tt.options)) }) } } func TestNewLogSink(t *testing.T) { const name = "name" for _, tt := range []struct { name string options []Option wantScopeRecords *logtest.ScopeRecords }{ { name: "with default options", wantScopeRecords: &logtest.ScopeRecords{Name: name}, }, { name: "with version and schema URL", options: []Option{ WithVersion("1.0"), WithSchemaURL("https://example.com"), }, wantScopeRecords: &logtest.ScopeRecords{ Name: name, Version: "1.0", SchemaURL: "https://example.com", }, }, } { t.Run(tt.name, func(t *testing.T) { provider := logtest.NewRecorder() var l *LogSink require.NotPanics(t, func() { l = NewLogSink(name, append( tt.options, WithLoggerProvider(provider), )...) }) require.NotNil(t, l) require.Len(t, provider.Result(), 1) got := provider.Result()[0] assert.Equal(t, tt.wantScopeRecords, got) }) } } func TestLogSink(t *testing.T) { const name = "name" for _, tt := range []struct { name string f func(*logr.Logger) wantRecords map[string][]log.Record }{ { name: "no_log", f: func(l *logr.Logger) {}, wantRecords: map[string][]log.Record{ name: {}, }, }, { name: "info", f: func(l *logr.Logger) { l.Info("msg") }, wantRecords: map[string][]log.Record{ name: { buildRecord(log.StringValue("msg"), time.Time{}, log.SeverityInfo, nil), }, }, }, { name: "info_multi_attrs", f: func(l *logr.Logger) { l.Info("msg", "struct", struct{ data int64 }{data: 1}, "bool", true, "duration", time.Minute, "float64", 3.14159, "int64", -2, "string", "str", "time", time.Unix(1000, 1000), "uint64", uint64(3), ) }, wantRecords: map[string][]log.Record{ name: { buildRecord(log.StringValue("msg"), time.Time{}, log.SeverityInfo, []log.KeyValue{ log.String("struct", "{data:1}"), log.Bool("bool", true), log.Int64("duration", 60_000_000_000), log.Float64("float64", 3.14159), log.Int64("int64", -2), log.String("string", "str"), log.Int64("time", time.Unix(1000, 1000).UnixNano()), log.Int64("uint64", 3), }), }, }, }, { name: "info_with_name", f: func(l *logr.Logger) { l.WithName("test").Info("info message with name") }, wantRecords: map[string][]log.Record{ name + "/test": { buildRecord(log.StringValue("info message with name"), time.Time{}, log.SeverityInfo, nil), }, }, }, { name: "info_with_name_nested", f: func(l *logr.Logger) { l.WithName("test").WithName("test").Info("info message with name") }, wantRecords: map[string][]log.Record{ name + "/test/test": { buildRecord(log.StringValue("info message with name"), time.Time{}, log.SeverityInfo, nil), }, }, }, { name: "info_with_attrs", f: func(l *logr.Logger) { l.WithValues("key", "value").Info("info message with attrs") }, wantRecords: map[string][]log.Record{ name: { buildRecord(log.StringValue("info message with attrs"), time.Time{}, log.SeverityInfo, []log.KeyValue{ log.String("key", "value"), }), }, }, }, { name: "info_with_attrs_nested", f: func(l *logr.Logger) { l.WithValues("key1", "value1").Info("info message with attrs", "key2", "value2") }, wantRecords: map[string][]log.Record{ name: { buildRecord(log.StringValue("info message with attrs"), time.Time{}, log.SeverityInfo, []log.KeyValue{ log.String("key1", "value1"), log.String("key2", "value2"), }), }, }, }, } { t.Run(tt.name, func(t *testing.T) { rec := logtest.NewRecorder() ls := NewLogSink(name, WithLoggerProvider(rec)) l := logr.New(ls) tt.f(&l) for k, v := range tt.wantRecords { found := false want := make([]logtest.EmittedRecord, len(v)) for i := range want { want[i] = logtest.EmittedRecord{Record: v[i]} } for _, s := range rec.Result() { if k == s.Name { assertRecords(t, want, s.Records) found = true } } assert.Truef(t, found, "want to find records with a scope named %q", k) } }) } } func buildRecord(body log.Value, timestamp time.Time, severity log.Severity, attrs []log.KeyValue) log.Record { var record log.Record record.SetBody(body) record.SetTimestamp(timestamp) record.SetSeverity(severity) record.AddAttributes(attrs...) return record } func assertRecords(t *testing.T, want, got []logtest.EmittedRecord) { t.Helper() assert.Equal(t, len(want), len(got)) for i, j := range want { logtest.AssertRecordEqual(t, j.Record, got[i].Record) } } open-telemetry-opentelemetry-go-contrib-e5abccb/bridges/otellogrus/000077500000000000000000000000001470323427300260675ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/bridges/otellogrus/example_test.go000066400000000000000000000011521470323427300311070ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otellogrus_test import ( "github.com/sirupsen/logrus" "go.opentelemetry.io/contrib/bridges/otellogrus" "go.opentelemetry.io/otel/log/noop" ) func Example() { // Use a working LoggerProvider implementation instead e.g. using go.opentelemetry.io/otel/sdk/log. provider := noop.NewLoggerProvider() // Create an *otellogrus.Hook and use it in your application. hook := otellogrus.NewHook("my/pkg/name", otellogrus.WithLoggerProvider(provider)) // Set the newly created hook as a global logrus hook logrus.AddHook(hook) } open-telemetry-opentelemetry-go-contrib-e5abccb/bridges/otellogrus/go.mod000066400000000000000000000011401470323427300271710ustar00rootroot00000000000000module go.opentelemetry.io/contrib/bridges/otellogrus go 1.22 require ( github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.9.0 go.opentelemetry.io/otel/log v0.7.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/otel v1.31.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect go.opentelemetry.io/otel/trace v1.31.0 // indirect golang.org/x/sys v0.26.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) open-telemetry-opentelemetry-go-contrib-e5abccb/bridges/otellogrus/go.sum000066400000000000000000000056401470323427300272270ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/log v0.7.0 h1:d1abJc0b1QQZADKvfe9JqqrfmPYQCz2tUSO+0XZmuV4= go.opentelemetry.io/otel/log v0.7.0/go.mod h1:2jf2z7uVfnzDNknKTO9G+ahcOAyWcp1fJmk/wJjULRo= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= open-telemetry-opentelemetry-go-contrib-e5abccb/bridges/otellogrus/hook.go000066400000000000000000000156671470323427300273750ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package otellogrus provides a [Hook], a [logrus.Hook] implementation that // can be used to bridge between the [github.com/sirupsen/logrus] API and // [OpenTelemetry]. // // # Record Conversion // // The [logrus.Entry] records are converted to OpenTelemetry [log.Record] in // the following way: // // - Time is set as the Timestamp. // - Message is set as the Body using a [log.StringValue]. // - Level is transformed and set as the Severity. The SeverityText is not // set. // - Fields are transformed and set as the attributes. // // The Level is transformed to the OpenTelemetry // Severity types. For example: // // - [logrus.DebugLevel] is transformed to [log.SeverityDebug] // - [logrus.InfoLevel] is transformed to [log.SeverityInfo] // - [logrus.WarnLevel] is transformed to [log.SeverityWarn] // - [logrus.ErrorLevel] is transformed to [log.SeverityError] // - [logrus.FatalLevel] is transformed to [log.SeverityFatal] // - [logrus.PanicLevel] is transformed to [log.SeverityFatal4] // // Field values are transformed based on their type into log attributes, or // into a string value if there is no matching type. // // [OpenTelemetry]: https://opentelemetry.io/docs/concepts/signals/logs/ package otellogrus // import "go.opentelemetry.io/contrib/bridges/otellogrus" import ( "fmt" "reflect" "github.com/sirupsen/logrus" "go.opentelemetry.io/otel/log" "go.opentelemetry.io/otel/log/global" ) type config struct { provider log.LoggerProvider version string schemaURL string levels []logrus.Level } func newConfig(options []Option) config { var c config for _, opt := range options { c = opt.apply(c) } if c.provider == nil { c.provider = global.GetLoggerProvider() } if c.levels == nil { c.levels = logrus.AllLevels } return c } func (c config) logger(name string) log.Logger { var opts []log.LoggerOption if c.version != "" { opts = append(opts, log.WithInstrumentationVersion(c.version)) } if c.schemaURL != "" { opts = append(opts, log.WithSchemaURL(c.schemaURL)) } return c.provider.Logger(name, opts...) } // Option configures a [Hook]. type Option interface { apply(config) config } type optFunc func(config) config func (f optFunc) apply(c config) config { return f(c) } // WithVersion returns an [Option] that configures the version of the // [log.Logger] used by a [Hook]. The version should be the version of the // package that is being logged. func WithVersion(version string) Option { return optFunc(func(c config) config { c.version = version return c }) } // WithSchemaURL returns an [Option] that configures the semantic convention // schema URL of the [log.Logger] used by a [Hook]. The schemaURL should be // the schema URL for the semantic conventions used in log records. func WithSchemaURL(schemaURL string) Option { return optFunc(func(c config) config { c.schemaURL = schemaURL return c }) } // WithLoggerProvider returns an [Option] that configures [log.LoggerProvider] // used by a [Hook]. // // By default if this Option is not provided, the Hook will use the global // LoggerProvider. func WithLoggerProvider(provider log.LoggerProvider) Option { return optFunc(func(c config) config { c.provider = provider return c }) } // WithLevels returns an [Option] that configures the log levels that will fire // the configured [Hook]. // // By default if this Option is not provided, the Hook will fire for all levels. // LoggerProvider. func WithLevels(l []logrus.Level) Option { return optFunc(func(c config) config { c.levels = l return c }) } // NewHook returns a new [Hook] to be used as a [logrus.Hook]. // // If [WithLoggerProvider] is not provided, the returned Hook will use the // global LoggerProvider. func NewHook(name string, options ...Option) *Hook { cfg := newConfig(options) return &Hook{ logger: cfg.logger(name), levels: cfg.levels, } } // Hook is a [logrus.Hook] that sends all logging records it receives to // OpenTelemetry. See package documentation for how conversions are made. type Hook struct { logger log.Logger levels []logrus.Level } // Levels returns the list of log levels we want to be sent to OpenTelemetry. func (h *Hook) Levels() []logrus.Level { return h.levels } // Fire handles the passed record, and sends it to OpenTelemetry. func (h *Hook) Fire(entry *logrus.Entry) error { ctx := entry.Context h.logger.Emit(ctx, h.convertEntry(entry)) return nil } func (h *Hook) convertEntry(e *logrus.Entry) log.Record { var record log.Record record.SetTimestamp(e.Time) record.SetBody(log.StringValue(e.Message)) record.SetSeverity(convertSeverity(e.Level)) record.AddAttributes(convertFields(e.Data)...) return record } func convertFields(fields logrus.Fields) []log.KeyValue { kvs := make([]log.KeyValue, 0, len(fields)) for k, v := range fields { kvs = append(kvs, log.KeyValue{ Key: k, Value: convertValue(v), }) } return kvs } func convertSeverity(level logrus.Level) log.Severity { switch level { case logrus.PanicLevel: // PanicLevel is not supported by OpenTelemetry, use Fatal4 as the highest severity. return log.SeverityFatal4 case logrus.FatalLevel: return log.SeverityFatal case logrus.ErrorLevel: return log.SeverityError case logrus.WarnLevel: return log.SeverityWarn case logrus.InfoLevel: return log.SeverityInfo case logrus.DebugLevel: return log.SeverityDebug case logrus.TraceLevel: return log.SeverityTrace default: // If the level is not recognized, use SeverityUndefined as the lowest severity. // we should never reach this point as logrus only uses the above levels. return log.SeverityUndefined } } func convertValue(v interface{}) log.Value { switch v := v.(type) { case bool: return log.BoolValue(v) case []byte: return log.BytesValue(v) case float64: return log.Float64Value(v) case int: return log.IntValue(v) case int64: return log.Int64Value(v) case string: return log.StringValue(v) } t := reflect.TypeOf(v) if t == nil { return log.Value{} } val := reflect.ValueOf(v) switch t.Kind() { case reflect.Struct: return log.StringValue(fmt.Sprintf("%+v", v)) case reflect.Slice, reflect.Array: items := make([]log.Value, 0, val.Len()) for i := 0; i < val.Len(); i++ { items = append(items, convertValue(val.Index(i).Interface())) } return log.SliceValue(items...) case reflect.Map: kvs := make([]log.KeyValue, 0, val.Len()) for _, k := range val.MapKeys() { var key string // If the key is a struct, use %+v to print the struct fields. if k.Kind() == reflect.Struct { key = fmt.Sprintf("%+v", k.Interface()) } else { key = fmt.Sprintf("%v", k.Interface()) } kvs = append(kvs, log.KeyValue{ Key: key, Value: convertValue(val.MapIndex(k).Interface()), }) } return log.MapValue(kvs...) case reflect.Ptr, reflect.Interface: return convertValue(val.Elem().Interface()) } return log.StringValue(fmt.Sprintf("unhandled attribute type: (%s) %+v", t, v)) } open-telemetry-opentelemetry-go-contrib-e5abccb/bridges/otellogrus/hook_test.go000066400000000000000000000245531470323427300304260ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otellogrus import ( "slices" "testing" "time" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/log" "go.opentelemetry.io/otel/log/embedded" "go.opentelemetry.io/otel/log/global" "go.opentelemetry.io/otel/log/logtest" ) type mockLoggerProvider struct { embedded.LoggerProvider } func (mockLoggerProvider) Logger(name string, options ...log.LoggerOption) log.Logger { return nil } func TestNewConfig(t *testing.T) { customLoggerProvider := mockLoggerProvider{} for _, tt := range []struct { name string options []Option wantConfig config }{ { name: "with no options", wantConfig: config{ provider: global.GetLoggerProvider(), levels: logrus.AllLevels, }, }, { name: "with a custom instrumentation scope", options: []Option{ WithVersion("42.0"), }, wantConfig: config{ version: "42.0", provider: global.GetLoggerProvider(), levels: logrus.AllLevels, }, }, { name: "with a custom logger provider", options: []Option{ WithLoggerProvider(customLoggerProvider), }, wantConfig: config{ provider: customLoggerProvider, levels: logrus.AllLevels, }, }, { name: "with custom log levels", options: []Option{ WithLevels([]logrus.Level{logrus.FatalLevel}), }, wantConfig: config{ provider: global.GetLoggerProvider(), levels: []logrus.Level{logrus.FatalLevel}, }, }, } { t.Run(tt.name, func(t *testing.T) { assert.Equal(t, tt.wantConfig, newConfig(tt.options)) }) } } func TestNewHook(t *testing.T) { const name = "name" provider := global.GetLoggerProvider() for _, tt := range []struct { name string options []Option wantLogger log.Logger }{ { name: "with the default options", wantLogger: provider.Logger(name), }, { name: "with a schema URL", options: []Option{ WithVersion("42.1"), WithSchemaURL("https://example.com"), }, wantLogger: provider.Logger(name, log.WithInstrumentationVersion("42.1"), log.WithSchemaURL("https://example.com"), ), }, } { t.Run(tt.name, func(t *testing.T) { hook := NewHook(name, tt.options...) assert.NotNil(t, hook) assert.Equal(t, tt.wantLogger, hook.logger) }) } } func TestHookLevels(t *testing.T) { for _, tt := range []struct { name string options []Option wantLevels []logrus.Level }{ { name: "with the default levels", wantLevels: logrus.AllLevels, }, { name: "with provided levels", options: []Option{ WithLevels([]logrus.Level{logrus.PanicLevel}), }, wantLevels: []logrus.Level{logrus.PanicLevel}, }, } { t.Run(tt.name, func(t *testing.T) { levels := NewHook("", tt.options...).Levels() assert.Equal(t, tt.wantLevels, levels) }) } } func TestHookFire(t *testing.T) { const name = "name" now := time.Now() for _, tt := range []struct { name string entry *logrus.Entry wantRecords map[string][]log.Record wantErr error }{ { name: "emits an empty log entry", entry: &logrus.Entry{}, wantRecords: map[string][]log.Record{ name: { buildRecord(log.StringValue(""), time.Time{}, log.SeverityFatal4, nil), }, }, }, { name: "emits a log entry with a timestamp", entry: &logrus.Entry{ Time: now, }, wantRecords: map[string][]log.Record{ name: { buildRecord(log.StringValue(""), now, log.SeverityFatal4, nil), }, }, }, { name: "emits a log entry with panic severity level", entry: &logrus.Entry{ Level: logrus.PanicLevel, }, wantRecords: map[string][]log.Record{ name: { buildRecord(log.StringValue(""), time.Time{}, log.SeverityFatal4, nil), }, }, }, { name: "emits a log entry with fatal severity level", entry: &logrus.Entry{ Level: logrus.FatalLevel, }, wantRecords: map[string][]log.Record{ name: { buildRecord(log.StringValue(""), time.Time{}, log.SeverityFatal, nil), }, }, }, { name: "emits a log entry with error severity level", entry: &logrus.Entry{ Level: logrus.ErrorLevel, }, wantRecords: map[string][]log.Record{ name: { buildRecord(log.StringValue(""), time.Time{}, log.SeverityError, nil), }, }, }, { name: "emits a log entry with warn severity level", entry: &logrus.Entry{ Level: logrus.WarnLevel, }, wantRecords: map[string][]log.Record{ name: { buildRecord(log.StringValue(""), time.Time{}, log.SeverityWarn, nil), }, }, }, { name: "emits a log entry with info severity level", entry: &logrus.Entry{ Level: logrus.InfoLevel, }, wantRecords: map[string][]log.Record{ name: { buildRecord(log.StringValue(""), time.Time{}, log.SeverityInfo, nil), }, }, }, { name: "emits a log entry with info severity level", entry: &logrus.Entry{ Level: logrus.DebugLevel, }, wantRecords: map[string][]log.Record{ name: { buildRecord(log.StringValue(""), time.Time{}, log.SeverityDebug, nil), }, }, }, { name: "emits a log entry with info severity level", entry: &logrus.Entry{ Level: logrus.TraceLevel, }, wantRecords: map[string][]log.Record{ name: { buildRecord(log.StringValue(""), time.Time{}, log.SeverityTrace, nil), }, }, }, { name: "emits a log entry with data", entry: &logrus.Entry{ Data: logrus.Fields{ "hello": "world", }, }, wantRecords: map[string][]log.Record{ name: { buildRecord(log.StringValue(""), time.Time{}, log.SeverityFatal4, []log.KeyValue{ log.String("hello", "world"), }), }, }, }, } { t.Run(tt.name, func(t *testing.T) { rec := logtest.NewRecorder() err := NewHook(name, WithLoggerProvider(rec)).Fire(tt.entry) assert.Equal(t, tt.wantErr, err) for k, v := range tt.wantRecords { found := false want := make([]logtest.EmittedRecord, len(v)) for i := range want { want[i] = logtest.EmittedRecord{Record: v[i]} } for _, s := range rec.Result() { if k == s.Name { assertRecords(t, want, s.Records) found = true } } assert.Truef(t, found, "want to find records with a scope named %q", k) } }) } } func TestConvertFields(t *testing.T) { for _, tt := range []struct { name string fields logrus.Fields wantKeyValue []log.KeyValue }{ { name: "with a boolean", fields: logrus.Fields{"hello": true}, wantKeyValue: []log.KeyValue{ log.Bool("hello", true), }, }, { name: "with a bytes array", fields: logrus.Fields{"hello": []byte("world")}, wantKeyValue: []log.KeyValue{ log.Bytes("hello", []byte("world")), }, }, { name: "with a float64", fields: logrus.Fields{"hello": 6.5}, wantKeyValue: []log.KeyValue{ log.Float64("hello", 6.5), }, }, { name: "with an int", fields: logrus.Fields{"hello": 42}, wantKeyValue: []log.KeyValue{ log.Int("hello", 42), }, }, { name: "with an int64", fields: logrus.Fields{"hello": int64(42)}, wantKeyValue: []log.KeyValue{ log.Int64("hello", 42), }, }, { name: "with a string", fields: logrus.Fields{"hello": "world"}, wantKeyValue: []log.KeyValue{ log.String("hello", "world"), }, }, { name: "with nil", fields: logrus.Fields{"hello": nil}, wantKeyValue: []log.KeyValue{ {Key: "hello", Value: log.Value{}}, }, }, { name: "with a struct", fields: logrus.Fields{"hello": struct{ Name string }{Name: "foobar"}}, wantKeyValue: []log.KeyValue{ log.String("hello", "{Name:foobar}"), }, }, { name: "with a slice", fields: logrus.Fields{"hello": []string{"foo", "bar"}}, wantKeyValue: []log.KeyValue{ log.Slice("hello", log.StringValue("foo"), log.StringValue("bar"), ), }, }, { name: "with an interface slice", fields: logrus.Fields{"hello": []interface{}{"foo", 42}}, wantKeyValue: []log.KeyValue{ log.Slice("hello", log.StringValue("foo"), log.Int64Value(42), ), }, }, { name: "with a map", fields: logrus.Fields{"hello": map[string]int{"answer": 42}}, wantKeyValue: []log.KeyValue{ log.Map("hello", log.Int("answer", 42)), }, }, { name: "with an interface map", fields: logrus.Fields{"hello": map[interface{}]interface{}{1: "question", "answer": 42}}, wantKeyValue: []log.KeyValue{ log.Map("hello", log.Int("answer", 42), log.String("1", "question")), }, }, { name: "with a nested map", fields: logrus.Fields{"hello": map[string]map[string]int{"sublevel": {"answer": 42}}}, wantKeyValue: []log.KeyValue{ log.Map("hello", log.Map("sublevel", log.Int("answer", 42))), }, }, { name: "with a struct map", fields: logrus.Fields{"hello": map[struct{ name string }]string{{name: "hello"}: "world"}}, wantKeyValue: []log.KeyValue{ log.Map("hello", log.String("{name:hello}", "world")), }, }, { name: "with a pointer to struct", fields: logrus.Fields{"hello": &struct{ Name string }{Name: "foobar"}}, wantKeyValue: []log.KeyValue{ log.String("hello", "{Name:foobar}"), }, }, } { t.Run(tt.name, func(t *testing.T) { assertKeyValues(t, tt.wantKeyValue, convertFields(tt.fields)) }) } } func BenchmarkHook(b *testing.B) { record := &logrus.Entry{ Data: map[string]interface{}{ "string": "hello", "int": 42, "float": 1.5, "bool": false, }, Message: "body", Time: time.Now(), Level: logrus.InfoLevel, } b.Run("Fire", func(b *testing.B) { hooks := make([]*Hook, b.N) for i := range hooks { hooks[i] = NewHook("") } b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { _ = hooks[n].Fire(record) } }) } func buildRecord(body log.Value, timestamp time.Time, severity log.Severity, attrs []log.KeyValue) log.Record { var record log.Record record.SetBody(body) record.SetTimestamp(timestamp) record.SetSeverity(severity) record.AddAttributes(attrs...) return record } func assertKeyValues(t *testing.T, want, got []log.KeyValue) { t.Helper() if !slices.EqualFunc(want, got, log.KeyValue.Equal) { t.Errorf("KeyValues are not equal:\nwant: %v\ngot: %v", want, got) } } func assertRecords(t *testing.T, want, got []logtest.EmittedRecord) { t.Helper() assert.Equal(t, len(want), len(got)) for i, j := range want { logtest.AssertRecordEqual(t, j.Record, got[i].Record) } } open-telemetry-opentelemetry-go-contrib-e5abccb/bridges/otelslog/000077500000000000000000000000001470323427300255205ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/bridges/otelslog/example_test.go000066400000000000000000000007531470323427300305460ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelslog_test import ( "go.opentelemetry.io/contrib/bridges/otelslog" "go.opentelemetry.io/otel/log/noop" ) func Example() { // Use a working LoggerProvider implementation instead e.g. using go.opentelemetry.io/otel/sdk/log. provider := noop.NewLoggerProvider() // Create an *slog.Logger and use it in your application. otelslog.NewLogger("my/pkg/name", otelslog.WithLoggerProvider(provider)) } open-telemetry-opentelemetry-go-contrib-e5abccb/bridges/otelslog/go.mod000066400000000000000000000010251470323427300266240ustar00rootroot00000000000000module go.opentelemetry.io/contrib/bridges/otelslog go 1.22 require ( github.com/stretchr/testify v1.9.0 go.opentelemetry.io/otel/log v0.7.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/otel v1.31.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect go.opentelemetry.io/otel/trace v1.31.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) open-telemetry-opentelemetry-go-contrib-e5abccb/bridges/otelslog/go.sum000066400000000000000000000041741470323427300266610ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/log v0.7.0 h1:d1abJc0b1QQZADKvfe9JqqrfmPYQCz2tUSO+0XZmuV4= go.opentelemetry.io/otel/log v0.7.0/go.mod h1:2jf2z7uVfnzDNknKTO9G+ahcOAyWcp1fJmk/wJjULRo= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= open-telemetry-opentelemetry-go-contrib-e5abccb/bridges/otelslog/handler.go000066400000000000000000000315071470323427300274720ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package otelslog provides [Handler], an [slog.Handler] implementation, that // can be used to bridge between the [log/slog] API and [OpenTelemetry]. // // # Record Conversion // // The [slog.Record] are converted to OpenTelemetry [log.Record] in the following // way: // // - Time is set as the Timestamp. // - Message is set as the Body using a [log.StringValue]. // - Level is transformed and set as the Severity. The SeverityText is not // set. // - PC is dropped. // - Attr are transformed and set as the Attributes. // // The Level is transformed by using the static offset to the OpenTelemetry // Severity types. For example: // // - [slog.LevelDebug] is transformed to [log.SeverityDebug] // - [slog.LevelInfo] is transformed to [log.SeverityInfo] // - [slog.LevelWarn] is transformed to [log.SeverityWarn] // - [slog.LevelError] is transformed to [log.SeverityError] // // Attribute values are transformed based on their [slog.Kind]: // // - [slog.KindAny] are transformed to [log.StringValue]. The value is // encoded using [fmt.Sprintf]. // - [slog.KindBool] are transformed to [log.BoolValue] directly. // - [slog.KindDuration] are transformed to [log.Int64Value] as nanoseconds. // - [slog.KindFloat64] are transformed to [log.Float64Value] directly. // - [slog.KindInt64] are transformed to [log.Int64Value] directly. // - [slog.KindString] are transformed to [log.StringValue] directly. // - [slog.KindTime] are transformed to [log.Int64Value] as nanoseconds since // the Unix epoch. // - [slog.KindUint64] are transformed to [log.Int64Value] using int64 // conversion. // - [slog.KindGroup] are transformed to [log.MapValue] using appropriate // transforms for each group value. // - [slog.KindLogValuer] the value is resolved and then transformed. // // [OpenTelemetry]: https://opentelemetry.io/docs/concepts/signals/logs/ package otelslog // import "go.opentelemetry.io/contrib/bridges/otelslog" import ( "context" "fmt" "log/slog" "slices" "go.opentelemetry.io/otel/log" "go.opentelemetry.io/otel/log/global" ) // NewLogger returns a new [slog.Logger] backed by a new [Handler]. See // [NewHandler] for details on how the backing Handler is created. func NewLogger(name string, options ...Option) *slog.Logger { return slog.New(NewHandler(name, options...)) } type config struct { provider log.LoggerProvider version string schemaURL string } func newConfig(options []Option) config { var c config for _, opt := range options { c = opt.apply(c) } if c.provider == nil { c.provider = global.GetLoggerProvider() } return c } func (c config) logger(name string) log.Logger { var opts []log.LoggerOption if c.version != "" { opts = append(opts, log.WithInstrumentationVersion(c.version)) } if c.schemaURL != "" { opts = append(opts, log.WithSchemaURL(c.schemaURL)) } return c.provider.Logger(name, opts...) } // Option configures a [Handler]. type Option interface { apply(config) config } type optFunc func(config) config func (f optFunc) apply(c config) config { return f(c) } // WithVersion returns an [Option] that configures the version of the // [log.Logger] used by a [Handler]. The version should be the version of the // package that is being logged. func WithVersion(version string) Option { return optFunc(func(c config) config { c.version = version return c }) } // WithSchemaURL returns an [Option] that configures the semantic convention // schema URL of the [log.Logger] used by a [Handler]. The schemaURL should be // the schema URL for the semantic conventions used in log records. func WithSchemaURL(schemaURL string) Option { return optFunc(func(c config) config { c.schemaURL = schemaURL return c }) } // WithLoggerProvider returns an [Option] that configures [log.LoggerProvider] // used by a [Handler] to create its [log.Logger]. // // By default if this Option is not provided, the Handler will use the global // LoggerProvider. func WithLoggerProvider(provider log.LoggerProvider) Option { return optFunc(func(c config) config { c.provider = provider return c }) } // Handler is an [slog.Handler] that sends all logging records it receives to // OpenTelemetry. See package documentation for how conversions are made. type Handler struct { // Ensure forward compatibility by explicitly making this not comparable. noCmp [0]func() //nolint: unused // This is indeed used. attrs *kvBuffer group *group logger log.Logger } // Compile-time check *Handler implements slog.Handler. var _ slog.Handler = (*Handler)(nil) // NewHandler returns a new [Handler] to be used as an [slog.Handler]. // // If [WithLoggerProvider] is not provided, the returned Handler will use the // global LoggerProvider. // // The provided name needs to uniquely identify the code being logged. This is // most commonly the package name of the code. If name is empty, the // [log.Logger] implementation may override this value with a default. func NewHandler(name string, options ...Option) *Handler { cfg := newConfig(options) return &Handler{logger: cfg.logger(name)} } // Handle handles the passed record. func (h *Handler) Handle(ctx context.Context, record slog.Record) error { h.logger.Emit(ctx, h.convertRecord(record)) return nil } func (h *Handler) convertRecord(r slog.Record) log.Record { var record log.Record record.SetTimestamp(r.Time) record.SetBody(log.StringValue(r.Message)) const sevOffset = slog.Level(log.SeverityDebug) - slog.LevelDebug record.SetSeverity(log.Severity(r.Level + sevOffset)) if h.attrs.Len() > 0 { record.AddAttributes(h.attrs.KeyValues()...) } n := r.NumAttrs() if h.group != nil { if n > 0 { buf := newKVBuffer(n) r.Attrs(buf.AddAttr) record.AddAttributes(h.group.KeyValue(buf.KeyValues()...)) } else { // A Handler should not output groups if there are no attributes. g := h.group.NextNonEmpty() if g != nil { record.AddAttributes(g.KeyValue()) } } } else if n > 0 { buf := newKVBuffer(n) r.Attrs(buf.AddAttr) record.AddAttributes(buf.KeyValues()...) } return record } // Enable returns true if the Handler is enabled to log for the provided // context and Level. Otherwise, false is returned if it is not enabled. func (h *Handler) Enabled(ctx context.Context, l slog.Level) bool { var param log.EnabledParameters const sevOffset = slog.Level(log.SeverityDebug) - slog.LevelDebug param.SetSeverity(log.Severity(l + sevOffset)) return h.logger.Enabled(ctx, param) } // WithAttrs returns a new [slog.Handler] based on h that will log using the // passed attrs. func (h *Handler) WithAttrs(attrs []slog.Attr) slog.Handler { h2 := *h if h2.group != nil { h2.group = h2.group.Clone() h2.group.AddAttrs(attrs) } else { if h2.attrs == nil { h2.attrs = newKVBuffer(len(attrs)) } else { h2.attrs = h2.attrs.Clone() } h2.attrs.AddAttrs(attrs) } return &h2 } // WithGroup returns a new [slog.Handler] based on h that will log all messages // and attributes within a group of the provided name. func (h *Handler) WithGroup(name string) slog.Handler { h2 := *h h2.group = &group{name: name, next: h2.group} return &h2 } // group represents a group received from slog. type group struct { // name is the name of the group. name string // attrs are the attributes associated with the group. attrs *kvBuffer // next points to the next group that holds this group. // // Groups are represented as map value types in OpenTelemetry. This means // that for an slog group hierarchy like the following ... // // WithGroup("G").WithGroup("H").WithGroup("I") // // the corresponding OpenTelemetry log value types will have the following // hierarchy ... // // KeyValue{ // Key: "G", // Value: []KeyValue{{ // Key: "H", // Value: []KeyValue{{ // Key: "I", // Value: []KeyValue{}, // }}, // }}, // } // // When attributes are recorded (i.e. Info("msg", "key", "value") or // WithAttrs("key", "value")) they need to be added to the "leaf" group. In // the above example, that would be group "I": // // KeyValue{ // Key: "G", // Value: []KeyValue{{ // Key: "H", // Value: []KeyValue{{ // Key: "I", // Value: []KeyValue{ // String("key", "value"), // }, // }}, // }}, // } // // Therefore, groups are structured as a linked-list with the "leaf" node // being the head of the list. Following the above example, the group data // representation would be ... // // *group{"I", next: *group{"H", next: *group{"G"}}} next *group } // NextNonEmpty returns the next group within g's linked-list that has // attributes (including g itself). If no group is found, nil is returned. func (g *group) NextNonEmpty() *group { if g == nil || g.attrs.Len() > 0 { return g } return g.next.NextNonEmpty() } // KeyValue returns group g containing kvs as a [log.KeyValue]. The value of // the returned KeyValue will be of type [log.KindMap]. // // The passed kvs are rendered in the returned value, but are not added to the // group. // // This does not check g. It is the callers responsibility to ensure g is // non-empty or kvs is non-empty so as to return a valid group representation // (according to slog). func (g *group) KeyValue(kvs ...log.KeyValue) log.KeyValue { // Assumes checking of group g already performed (i.e. non-empty). out := log.Map(g.name, g.attrs.KeyValues(kvs...)...) g = g.next for g != nil { // A Handler should not output groups if there are no attributes. if g.attrs.Len() > 0 { out = log.Map(g.name, g.attrs.KeyValues(out)...) } g = g.next } return out } // Clone returns a copy of g. func (g *group) Clone() *group { if g == nil { return g } g2 := *g g2.attrs = g2.attrs.Clone() return &g2 } // AddAttrs add attrs to g. func (g *group) AddAttrs(attrs []slog.Attr) { if g.attrs == nil { g.attrs = newKVBuffer(len(attrs)) } g.attrs.AddAttrs(attrs) } type kvBuffer struct { data []log.KeyValue } func newKVBuffer(n int) *kvBuffer { return &kvBuffer{data: make([]log.KeyValue, 0, n)} } // Len returns the number of [log.KeyValue] held by b. func (b *kvBuffer) Len() int { if b == nil { return 0 } return len(b.data) } // Clone returns a copy of b. func (b *kvBuffer) Clone() *kvBuffer { if b == nil { return nil } return &kvBuffer{data: slices.Clone(b.data)} } // KeyValues returns kvs appended to the [log.KeyValue] held by b. func (b *kvBuffer) KeyValues(kvs ...log.KeyValue) []log.KeyValue { if b == nil { return kvs } return append(b.data, kvs...) } // AddAttrs adds attrs to b. func (b *kvBuffer) AddAttrs(attrs []slog.Attr) { b.data = slices.Grow(b.data, len(attrs)) for _, a := range attrs { _ = b.AddAttr(a) } } // AddAttr adds attr to b and returns true. // // This is designed to be passed to the AddAttributes method of an // [slog.Record]. // // If attr is a group with an empty key, its values will be flattened. // // If attr is empty, it will be dropped. func (b *kvBuffer) AddAttr(attr slog.Attr) bool { if attr.Key == "" { if attr.Value.Kind() == slog.KindGroup { // A Handler should inline the Attrs of a group with an empty key. for _, a := range attr.Value.Group() { b.data = append(b.data, log.KeyValue{ Key: a.Key, Value: convertValue(a.Value), }) } return true } if attr.Value.Any() == nil { // A Handler should ignore an empty Attr. return true } } b.data = append(b.data, log.KeyValue{ Key: attr.Key, Value: convertValue(attr.Value), }) return true } func convertValue(v slog.Value) log.Value { switch v.Kind() { case slog.KindAny: return log.StringValue(fmt.Sprintf("%+v", v.Any())) case slog.KindBool: return log.BoolValue(v.Bool()) case slog.KindDuration: return log.Int64Value(v.Duration().Nanoseconds()) case slog.KindFloat64: return log.Float64Value(v.Float64()) case slog.KindInt64: return log.Int64Value(v.Int64()) case slog.KindString: return log.StringValue(v.String()) case slog.KindTime: return log.Int64Value(v.Time().UnixNano()) case slog.KindUint64: const maxInt64 = ^uint64(0) >> 1 u := v.Uint64() if u > maxInt64 { return log.Float64Value(float64(u)) } return log.Int64Value(int64(u)) // nolint:gosec // Overflow checked above. case slog.KindGroup: g := v.Group() buf := newKVBuffer(len(g)) buf.AddAttrs(g) return log.MapValue(buf.data...) case slog.KindLogValuer: return convertValue(v.Resolve()) default: // Try to handle this as gracefully as possible. // // Don't panic here. The goal here is to have developers find this // first if a new slog.Kind is added. A test on the new kind will find // this malformed attribute as well as a panic. However, it is // preferable to have user's open issue asking why their attributes // have a "unhandled: " prefix than say that their code is panicking. return log.StringValue(fmt.Sprintf("unhandled: (%s) %+v", v.Kind(), v.Any())) } } open-telemetry-opentelemetry-go-contrib-e5abccb/bridges/otelslog/handler_test.go000066400000000000000000000350461470323427300305330ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Copyright 2023 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package otelslog import ( "context" "fmt" "log/slog" "reflect" "runtime" "testing" "testing/slogtest" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/log" "go.opentelemetry.io/otel/log/embedded" "go.opentelemetry.io/otel/log/global" ) var now = time.Now() func TestNewLogger(t *testing.T) { assert.IsType(t, &Handler{}, NewLogger("").Handler()) } // embeddedLogger is a type alias so the embedded.Logger type doesn't conflict // with the Logger method of the recorder when it is embedded. type embeddedLogger = embedded.Logger // nolint:unused // Used below. type scope struct { Name, Version, SchemaURL string } // recorder records all [log.Record]s it is ased to emit. type recorder struct { embedded.LoggerProvider embeddedLogger // nolint:unused // Used to embed embedded.Logger. // Records are the records emitted. Records []log.Record // Scope is the Logger scope recorder received when Logger was called. Scope scope // MinSeverity is the minimum severity the recorder will return true for // when Enabled is called (unless enableKey is set). MinSeverity log.Severity } func (r *recorder) Logger(name string, opts ...log.LoggerOption) log.Logger { cfg := log.NewLoggerConfig(opts...) r.Scope = scope{ Name: name, Version: cfg.InstrumentationVersion(), SchemaURL: cfg.SchemaURL(), } return r } type enablerKey uint var enableKey enablerKey func (r *recorder) Enabled(ctx context.Context, param log.EnabledParameters) bool { lvl, ok := param.Severity() if !ok { return true } return ctx.Value(enableKey) != nil || lvl >= r.MinSeverity } func (r *recorder) Emit(_ context.Context, record log.Record) { r.Records = append(r.Records, record) } func (r *recorder) Results() []map[string]any { out := make([]map[string]any, len(r.Records)) for i := range out { r := r.Records[i] m := make(map[string]any) if tStamp := r.Timestamp(); !tStamp.IsZero() { m[slog.TimeKey] = tStamp } if lvl := r.Severity(); lvl != 0 { m[slog.LevelKey] = lvl - 9 } if body := r.Body(); body.Kind() != log.KindEmpty { m[slog.MessageKey] = value2Result(body) } r.WalkAttributes(func(kv log.KeyValue) bool { m[kv.Key] = value2Result(kv.Value) return true }) out[i] = m } return out } func value2Result(v log.Value) any { switch v.Kind() { case log.KindBool: return v.AsBool() case log.KindFloat64: return v.AsFloat64() case log.KindInt64: return v.AsInt64() case log.KindString: return v.AsString() case log.KindBytes: return v.AsBytes() case log.KindSlice: return v.AsSlice() case log.KindMap: m := make(map[string]any) for _, val := range v.AsMap() { m[val.Key] = value2Result(val.Value) } return m } return nil } // testCase represents a complete setup/run/check of an slog handler to test. // It is based on the testCase from "testing/slogtest" (1.22.1). type testCase struct { // Subtest name. name string // If non-empty, explanation explains the violated constraint. explanation string // f executes a single log event using its argument logger. // So that mkdescs.sh can generate the right description, // the body of f must appear on a single line whose first // non-whitespace characters are "l.". f func(*slog.Logger) // If mod is not nil, it is called to modify the Record // generated by the Logger before it is passed to the Handler. mod func(*slog.Record) // checks is a list of checks to run on the result. Each item is a slice of // checks that will be evaluated for the corresponding record emitted. checks [][]check } // copied from slogtest (1.22.1). type check func(map[string]any) string // copied from slogtest (1.22.1). func hasKey(key string) check { return func(m map[string]any) string { if _, ok := m[key]; !ok { return fmt.Sprintf("missing key %q", key) } return "" } } // copied from slogtest (1.22.1). func missingKey(key string) check { return func(m map[string]any) string { if _, ok := m[key]; ok { return fmt.Sprintf("unexpected key %q", key) } return "" } } // copied from slogtest (1.22.1). func hasAttr(key string, wantVal any) check { return func(m map[string]any) string { if s := hasKey(key)(m); s != "" { return s } gotVal := m[key] if !reflect.DeepEqual(gotVal, wantVal) { return fmt.Sprintf("%q: got %#v, want %#v", key, gotVal, wantVal) } return "" } } // copied from slogtest (1.22.1). func inGroup(name string, c check) check { return func(m map[string]any) string { v, ok := m[name] if !ok { return fmt.Sprintf("missing group %q", name) } g, ok := v.(map[string]any) if !ok { return fmt.Sprintf("value for group %q is not map[string]any", name) } return c(g) } } // copied from slogtest (1.22.1). func withSource(s string) string { _, file, line, ok := runtime.Caller(1) if !ok { panic("runtime.Caller failed") } return fmt.Sprintf("%s (%s:%d)", s, file, line) } // copied from slogtest (1.22.1). type wrapper struct { slog.Handler mod func(*slog.Record) } // copied from slogtest (1.22.1). func (h *wrapper) Handle(ctx context.Context, r slog.Record) error { h.mod(&r) return h.Handler.Handle(ctx, r) } func TestSLogHandler(t *testing.T) { cases := []testCase{ { name: "Values", explanation: withSource("all slog Values need to be supported"), f: func(l *slog.Logger) { l.Info( "msg", "any", struct{ data int64 }{data: 1}, "bool", true, "duration", time.Minute, "float64", 3.14159, "int64", -2, "string", "str", "time", now, "uint64", uint64(3), // KindGroup and KindLogValuer are left for slogtest.TestHandler. ) }, checks: [][]check{{ hasKey(slog.TimeKey), hasKey(slog.LevelKey), hasAttr("any", "{data:1}"), hasAttr("bool", true), hasAttr("duration", int64(time.Minute)), hasAttr("float64", 3.14159), hasAttr("int64", int64(-2)), hasAttr("string", "str"), hasAttr("time", now.UnixNano()), hasAttr("uint64", int64(3)), }}, }, { name: "multi-messages", explanation: withSource("this test expects multiple independent messages"), f: func(l *slog.Logger) { l.Info("one") l.Info("two") }, checks: [][]check{{ hasKey(slog.TimeKey), hasKey(slog.LevelKey), hasAttr(slog.MessageKey, "one"), }, { hasKey(slog.TimeKey), hasKey(slog.LevelKey), hasAttr(slog.MessageKey, "two"), }}, }, { name: "multi-attrs", explanation: withSource("attributes from one message do not affect another"), f: func(l *slog.Logger) { l.Info("one", "k", "v") l.Info("two") }, checks: [][]check{{ hasAttr("k", "v"), }, { missingKey("k"), }}, }, { name: "independent-WithAttrs", explanation: withSource("a Handler should only include attributes from its own WithAttr origin"), f: func(l *slog.Logger) { l1 := l.With("a", "b") l2 := l1.With("c", "d") l3 := l1.With("e", "f") l3.Info("msg", "k", "v") l2.Info("msg", "k", "v") l1.Info("msg", "k", "v") l.Info("msg", "k", "v") }, checks: [][]check{{ hasAttr("a", "b"), hasAttr("e", "f"), hasAttr("k", "v"), }, { hasAttr("a", "b"), hasAttr("c", "d"), hasAttr("k", "v"), missingKey("e"), }, { hasAttr("a", "b"), hasAttr("k", "v"), missingKey("c"), missingKey("e"), }, { hasAttr("k", "v"), missingKey("a"), missingKey("c"), missingKey("e"), }}, }, { name: "independent-WithGroup", explanation: withSource("a Handler should only include attributes from its own WithGroup origin"), f: func(l *slog.Logger) { l1 := l.WithGroup("G").With("a", "b") l2 := l1.WithGroup("H").With("c", "d") l3 := l1.WithGroup("I").With("e", "f") l3.Info("msg", "k", "v") l2.Info("msg", "k", "v") l1.Info("msg", "k", "v") l.Info("msg", "k", "v") }, checks: [][]check{{ hasKey(slog.TimeKey), hasKey(slog.LevelKey), hasAttr(slog.MessageKey, "msg"), missingKey("a"), missingKey("c"), missingKey("H"), inGroup("G", hasAttr("a", "b")), inGroup("G", inGroup("I", hasAttr("e", "f"))), inGroup("G", inGroup("I", hasAttr("k", "v"))), }, { hasKey(slog.TimeKey), hasKey(slog.LevelKey), hasAttr(slog.MessageKey, "msg"), missingKey("a"), missingKey("c"), inGroup("G", hasAttr("a", "b")), inGroup("G", inGroup("H", hasAttr("c", "d"))), inGroup("G", inGroup("H", hasAttr("k", "v"))), }, { hasKey(slog.TimeKey), hasKey(slog.LevelKey), hasAttr(slog.MessageKey, "msg"), missingKey("a"), missingKey("c"), missingKey("H"), inGroup("G", hasAttr("a", "b")), inGroup("G", hasAttr("k", "v")), }, { hasKey(slog.TimeKey), hasKey(slog.LevelKey), hasAttr("k", "v"), hasAttr(slog.MessageKey, "msg"), missingKey("a"), missingKey("c"), missingKey("G"), missingKey("H"), }}, }, { name: "independent-WithGroup.WithAttrs", explanation: withSource("a Handler should only include group attributes from its own WithAttr origin"), f: func(l *slog.Logger) { l = l.WithGroup("G") l.With("a", "b").Info("msg", "k", "v") l.With("c", "d").Info("msg", "k", "v") }, checks: [][]check{{ inGroup("G", hasAttr("a", "b")), inGroup("G", hasAttr("k", "v")), inGroup("G", missingKey("c")), }, { inGroup("G", hasAttr("c", "d")), inGroup("G", hasAttr("k", "v")), inGroup("G", missingKey("a")), }}, }, } // Based on slogtest.Run. for _, c := range cases { t.Run(c.name, func(t *testing.T) { r := new(recorder) var h slog.Handler = NewHandler("", WithLoggerProvider(r)) if c.mod != nil { h = &wrapper{h, c.mod} } l := slog.New(h) c.f(l) got := r.Results() if len(got) != len(c.checks) { t.Fatalf("missing record checks: %d records, %d checks", len(got), len(c.checks)) } for i, checks := range c.checks { for _, check := range checks { if p := check(got[i]); p != "" { t.Errorf("%s: %s", p, c.explanation) } } } }) } t.Run("slogtest.TestHandler", func(t *testing.T) { r := new(recorder) h := NewHandler("", WithLoggerProvider(r)) // TODO: use slogtest.Run when Go 1.21 is no longer supported. err := slogtest.TestHandler(h, r.Results) if err != nil { t.Fatal(err) } }) } func TestNewHandlerConfiguration(t *testing.T) { name := "name" t.Run("Default", func(t *testing.T) { r := new(recorder) prev := global.GetLoggerProvider() defer global.SetLoggerProvider(prev) global.SetLoggerProvider(r) var h *Handler require.NotPanics(t, func() { h = NewHandler(name) }) require.NotNil(t, h.logger) require.IsType(t, &recorder{}, h.logger) l := h.logger.(*recorder) want := scope{Name: name} assert.Equal(t, want, l.Scope) }) t.Run("Options", func(t *testing.T) { r := new(recorder) var h *Handler require.NotPanics(t, func() { h = NewHandler( name, WithLoggerProvider(r), WithVersion("ver"), WithSchemaURL("url"), ) }) require.NotNil(t, h.logger) require.IsType(t, &recorder{}, h.logger) l := h.logger.(*recorder) scope := scope{Name: "name", Version: "ver", SchemaURL: "url"} assert.Equal(t, scope, l.Scope) }) } func TestHandlerEnabled(t *testing.T) { r := new(recorder) r.MinSeverity = log.SeverityInfo h := NewHandler("name", WithLoggerProvider(r)) ctx := context.Background() assert.False(t, h.Enabled(ctx, slog.LevelDebug), "level conversion: permissive") assert.True(t, h.Enabled(ctx, slog.LevelInfo), "level conversion: restrictive") ctx = context.WithValue(ctx, enableKey, true) assert.True(t, h.Enabled(ctx, slog.LevelDebug), "context not passed") } func BenchmarkHandler(b *testing.B) { var ( h slog.Handler err error ) attrs10 := []slog.Attr{ slog.String("1", "1"), slog.Int64("2", 2), slog.Int("3", 3), slog.Uint64("4", 4), slog.Float64("5", 5.), slog.Bool("6", true), slog.Time("7", time.Now()), slog.Duration("8", time.Second), slog.Any("9", 9), slog.Any("10", "10"), } attrs5 := attrs10[:5] record := slog.NewRecord(time.Now(), slog.LevelInfo, "body", 0) ctx := context.Background() b.Run("Handle", func(b *testing.B) { handlers := make([]*Handler, b.N) for i := range handlers { handlers[i] = NewHandler("") } b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { err = handlers[n].Handle(ctx, record) } }) b.Run("WithAttrs", func(b *testing.B) { b.Run("5", func(b *testing.B) { handlers := make([]*Handler, b.N) for i := range handlers { handlers[i] = NewHandler("") } b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { h = handlers[n].WithAttrs(attrs5) } }) b.Run("10", func(b *testing.B) { handlers := make([]*Handler, b.N) for i := range handlers { handlers[i] = NewHandler("") } b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { h = handlers[n].WithAttrs(attrs10) } }) }) b.Run("WithGroup", func(b *testing.B) { handlers := make([]*Handler, b.N) for i := range handlers { handlers[i] = NewHandler("") } b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { h = handlers[n].WithGroup("group") } }) b.Run("WithGroup.WithAttrs", func(b *testing.B) { b.Run("5", func(b *testing.B) { handlers := make([]*Handler, b.N) for i := range handlers { handlers[i] = NewHandler("") } b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { h = handlers[n].WithGroup("group").WithAttrs(attrs5) } }) b.Run("10", func(b *testing.B) { handlers := make([]*Handler, b.N) for i := range handlers { handlers[i] = NewHandler("") } b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { h = handlers[n].WithGroup("group").WithAttrs(attrs10) } }) }) b.Run("(WithGroup.WithAttrs).Handle", func(b *testing.B) { b.Run("5", func(b *testing.B) { handlers := make([]slog.Handler, b.N) for i := range handlers { handlers[i] = NewHandler("").WithGroup("group").WithAttrs(attrs5) } b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { err = handlers[n].Handle(ctx, record) } }) b.Run("10", func(b *testing.B) { handlers := make([]slog.Handler, b.N) for i := range handlers { handlers[i] = NewHandler("").WithGroup("group").WithAttrs(attrs10) } b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { err = handlers[n].Handle(ctx, record) } }) }) _, _ = h, err } open-telemetry-opentelemetry-go-contrib-e5abccb/bridges/otelzap/000077500000000000000000000000001470323427300253465ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/bridges/otelzap/README.md000066400000000000000000000002741470323427300266300ustar00rootroot00000000000000# OpenTelemetry Zap Log Bridge [![Go Reference](https://pkg.go.dev/badge/go.opentelemetry.io/contrib/bridges/otelzap.svg)](https://pkg.go.dev/go.opentelemetry.io/contrib/bridges/otelzap) open-telemetry-opentelemetry-go-contrib-e5abccb/bridges/otelzap/core.go000066400000000000000000000155461470323427300266400ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package otelzap provides a bridge between the [go.uber.org/zap] and // [OpenTelemetry]. // // # Record Conversion // // The [zapcore.Entry] and [zapcore.Field] are converted to OpenTelemetry [log.Record] in the following // way: // // - Time is set as the Timestamp. // - Message is set as the Body using a [log.StringValue]. // - Level is transformed and set as the Severity. The SeverityText is also // set. // - Fields are transformed and set as the Attributes. // - Field value of type [context.Context] is used as context when emitting log records. // - For named loggers, LoggerName is used to access [log.Logger] from [log.LoggerProvider] // // The Level is transformed to the OpenTelemetry Severity types in the following way. // // - [zapcore.DebugLevel] is transformed to [log.SeverityDebug] // - [zapcore.InfoLevel] is transformed to [log.SeverityInfo] // - [zapcore.WarnLevel] is transformed to [log.SeverityWarn] // - [zapcore.ErrorLevel] is transformed to [log.SeverityError] // - [zapcore.DPanicLevel] is transformed to [log.SeverityFatal1] // - [zapcore.PanicLevel] is transformed to [log.SeverityFatal2] // - [zapcore.FatalLevel] is transformed to [log.SeverityFatal3] // // Fields are transformed based on their type into log attributes, or into a string value if there is no matching type. // // [OpenTelemetry]: https://opentelemetry.io/docs/concepts/signals/logs/ package otelzap // import "go.opentelemetry.io/contrib/bridges/otelzap" import ( "context" "slices" "go.uber.org/zap/zapcore" "go.opentelemetry.io/otel/log" "go.opentelemetry.io/otel/log/global" ) type config struct { provider log.LoggerProvider version string schemaURL string } func newConfig(options []Option) config { var c config for _, opt := range options { c = opt.apply(c) } if c.provider == nil { c.provider = global.GetLoggerProvider() } return c } // Option configures a [Core]. type Option interface { apply(config) config } type optFunc func(config) config func (f optFunc) apply(c config) config { return f(c) } // WithVersion returns an [Option] that configures the version of the // [log.Logger] used by a [Core]. The version should be the version of the // package that is being logged. func WithVersion(version string) Option { return optFunc(func(c config) config { c.version = version return c }) } // WithSchemaURL returns an [Option] that configures the semantic convention // schema URL of the [log.Logger] used by a [Core]. The schemaURL should be // the schema URL for the semantic conventions used in log records. func WithSchemaURL(schemaURL string) Option { return optFunc(func(c config) config { c.schemaURL = schemaURL return c }) } // WithLoggerProvider returns an [Option] that configures [log.LoggerProvider] // used by a [Core] to create its [log.Logger]. // // By default if this Option is not provided, the Handler will use the global // LoggerProvider. func WithLoggerProvider(provider log.LoggerProvider) Option { return optFunc(func(c config) config { c.provider = provider return c }) } // Core is a [zapcore.Core] that sends logging records to OpenTelemetry. type Core struct { provider log.LoggerProvider logger log.Logger opts []log.LoggerOption attr []log.KeyValue ctx context.Context } // Compile-time check *Core implements zapcore.Core. var _ zapcore.Core = (*Core)(nil) // NewCore creates a new [zapcore.Core] that can be used with [go.uber.org/zap.New]. // The name should be the package import path that is being logged. // The name is ignored for named loggers created using [go.uber.org/zap.Logger.Named]. func NewCore(name string, opts ...Option) *Core { cfg := newConfig(opts) var loggerOpts []log.LoggerOption if cfg.version != "" { loggerOpts = append(loggerOpts, log.WithInstrumentationVersion(cfg.version)) } if cfg.schemaURL != "" { loggerOpts = append(loggerOpts, log.WithSchemaURL(cfg.schemaURL)) } logger := cfg.provider.Logger(name, loggerOpts...) return &Core{ provider: cfg.provider, logger: logger, opts: loggerOpts, ctx: context.Background(), } } // Enabled decides whether a given logging level is enabled when logging a message. func (o *Core) Enabled(level zapcore.Level) bool { param := log.EnabledParameters{} param.SetSeverity(convertLevel(level)) return o.logger.Enabled(context.Background(), param) } // With adds structured context to the Core. func (o *Core) With(fields []zapcore.Field) zapcore.Core { cloned := o.clone() if len(fields) > 0 { ctx, attrbuf := convertField(fields) if ctx != nil { cloned.ctx = ctx } cloned.attr = append(cloned.attr, attrbuf...) } return cloned } func (o *Core) clone() *Core { return &Core{ provider: o.provider, opts: o.opts, logger: o.logger, attr: slices.Clone(o.attr), ctx: o.ctx, } } // Sync flushes buffered logs (if any). func (o *Core) Sync() error { return nil } // Check determines whether the supplied Entry should be logged. // If the entry should be logged, the Core adds itself to the CheckedEntry and returns the result. func (o *Core) Check(ent zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry { param := log.EnabledParameters{} param.SetSeverity(convertLevel(ent.Level)) logger := o.logger if ent.LoggerName != "" { logger = o.provider.Logger(ent.LoggerName, o.opts...) } if logger.Enabled(context.Background(), param) { return ce.AddCore(ent, o) } return ce } // Write method encodes zap fields to OTel logs and emits them. func (o *Core) Write(ent zapcore.Entry, fields []zapcore.Field) error { r := log.Record{} r.SetTimestamp(ent.Time) r.SetBody(log.StringValue(ent.Message)) r.SetSeverity(convertLevel(ent.Level)) r.SetSeverityText(ent.Level.String()) r.AddAttributes(o.attr...) if len(fields) > 0 { ctx, attrbuf := convertField(fields) if ctx != nil { o.ctx = ctx } r.AddAttributes(attrbuf...) } logger := o.logger if ent.LoggerName != "" { logger = o.provider.Logger(ent.LoggerName, o.opts...) } logger.Emit(o.ctx, r) return nil } func convertField(fields []zapcore.Field) (context.Context, []log.KeyValue) { var ctx context.Context enc := newObjectEncoder(len(fields)) for _, field := range fields { if ctxFld, ok := field.Interface.(context.Context); ok { ctx = ctxFld continue } field.AddTo(enc) } enc.calculate(enc.root) return ctx, enc.root.attrs } func convertLevel(level zapcore.Level) log.Severity { switch level { case zapcore.DebugLevel: return log.SeverityDebug case zapcore.InfoLevel: return log.SeverityInfo case zapcore.WarnLevel: return log.SeverityWarn case zapcore.ErrorLevel: return log.SeverityError case zapcore.DPanicLevel: return log.SeverityFatal1 case zapcore.PanicLevel: return log.SeverityFatal2 case zapcore.FatalLevel: return log.SeverityFatal3 default: return log.SeverityUndefined } } open-telemetry-opentelemetry-go-contrib-e5abccb/bridges/otelzap/core_test.go000066400000000000000000000204721470323427300276710ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelzap import ( "context" "fmt" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap" "go.uber.org/zap/zapcore" "go.opentelemetry.io/otel/log" "go.opentelemetry.io/otel/log/global" "go.opentelemetry.io/otel/log/logtest" ) var ( testMessage = "log message" loggerName = "name" testKey = "key" testValue = "value" testEntry = zapcore.Entry{ Level: zap.InfoLevel, Message: testMessage, } ) func TestCore(t *testing.T) { rec := logtest.NewRecorder() zc := NewCore(loggerName, WithLoggerProvider(rec)) logger := zap.New(zc) t.Run("Write", func(t *testing.T) { logger.Info(testMessage, zap.String(testKey, testValue)) got := rec.Result()[0].Records[0] assert.Equal(t, testMessage, got.Body().AsString()) assert.Equal(t, log.SeverityInfo, got.Severity()) assert.Equal(t, zap.InfoLevel.String(), got.SeverityText()) assert.Equal(t, 1, got.AttributesLen()) got.WalkAttributes(func(kv log.KeyValue) bool { assert.Equal(t, testKey, kv.Key) assert.Equal(t, testValue, value2Result(kv.Value)) return true }) }) rec.Reset() t.Run("Write Context", func(t *testing.T) { ctx := context.Background() ctx = context.WithValue(ctx, testEntry, true) logger.Info(testMessage, zap.Any("ctx", ctx)) got := rec.Result()[0].Records[0] assert.Equal(t, got.Context(), ctx) }) rec.Reset() t.Run("With Context", func(t *testing.T) { ctx := context.Background() ctx = context.WithValue(ctx, testEntry, false) childlogger := logger.With(zap.Reflect("ctx", ctx)) childlogger.Info(testMessage) got := rec.Result()[0].Records[0] assert.Equal(t, got.Context(), ctx) }) rec.Reset() // test child logger with accumulated fields t.Run("With", func(t *testing.T) { testCases := [][]string{{"test1", "value1"}, {"test2", "value2"}} childlogger := logger.With(zap.String(testCases[0][0], testCases[0][1])) childlogger.Info(testMessage, zap.String(testCases[1][0], testCases[1][1])) got := rec.Result()[0].Records[0] assert.Equal(t, testMessage, got.Body().AsString()) assert.Equal(t, log.SeverityInfo, got.Severity()) assert.Equal(t, zap.InfoLevel.String(), got.SeverityText()) assert.Equal(t, 2, got.AttributesLen()) index := 0 got.WalkAttributes(func(kv log.KeyValue) bool { assert.Equal(t, testCases[index][0], kv.Key) assert.Equal(t, testCases[index][1], value2Result(kv.Value)) index++ return true }) }) rec.Reset() t.Run("Named", func(t *testing.T) { name := "my/pkg" childlogger := logger.Named(name) childlogger.Info(testMessage, zap.String(testKey, testValue)) found := false for _, got := range rec.Result() { found = got.Name == name if found { break } } assert.True(t, found) }) rec.Reset() t.Run("WithMultiple", func(t *testing.T) { testCases := [][]string{{"test1", "value1"}, {"test2", "value2"}, {"test3", "value3"}} childlogger := logger.With(zap.String(testCases[0][0], testCases[0][1])) childlogger2 := childlogger.With(zap.String(testCases[1][0], testCases[1][1])) childlogger2.Info(testMessage, zap.String(testCases[2][0], testCases[2][1])) got := rec.Result()[0].Records[0] assert.Equal(t, testMessage, got.Body().AsString()) assert.Equal(t, log.SeverityInfo, got.Severity()) assert.Equal(t, zap.InfoLevel.String(), got.SeverityText()) assert.Equal(t, 3, got.AttributesLen()) index := 0 got.WalkAttributes(func(kv log.KeyValue) bool { assert.Equal(t, testCases[index][0], kv.Key) assert.Equal(t, testCases[index][1], value2Result(kv.Value)) index++ return true }) }) } func TestCoreEnabled(t *testing.T) { enabledFunc := func(c context.Context, param log.EnabledParameters) bool { lvl, ok := param.Severity() if !ok { return true } return lvl >= log.SeverityInfo } r := logtest.NewRecorder(logtest.WithEnabledFunc(enabledFunc)) logger := zap.New(NewCore(loggerName, WithLoggerProvider(r))) logger.Debug(testMessage) assert.Empty(t, r.Result()[0].Records) if ce := logger.Check(zap.DebugLevel, testMessage); ce != nil { ce.Write() } assert.Empty(t, r.Result()[0].Records) if ce := logger.Check(zap.InfoLevel, testMessage); ce != nil { ce.Write() } require.Len(t, r.Result()[0].Records, 1) got := r.Result()[0].Records[0] assert.Equal(t, testMessage, got.Body().AsString()) assert.Equal(t, log.SeverityInfo, got.Severity()) assert.Equal(t, zap.InfoLevel.String(), got.SeverityText()) } func TestNewCoreConfiguration(t *testing.T) { t.Run("Default", func(t *testing.T) { r := logtest.NewRecorder() prev := global.GetLoggerProvider() defer global.SetLoggerProvider(prev) global.SetLoggerProvider(r) var h *Core require.NotPanics(t, func() { h = NewCore(loggerName) }) require.NotNil(t, h.logger) require.Len(t, r.Result(), 1) want := &logtest.ScopeRecords{Name: loggerName} got := r.Result()[0] assert.Equal(t, want, got) }) t.Run("Options", func(t *testing.T) { r := logtest.NewRecorder() var h *Core require.NotPanics(t, func() { h = NewCore( loggerName, WithLoggerProvider(r), WithVersion("1.0.0"), WithSchemaURL("url"), ) }) require.NotNil(t, h.logger) require.Len(t, r.Result(), 1) want := &logtest.ScopeRecords{Name: loggerName, Version: "1.0.0", SchemaURL: "url"} got := r.Result()[0] assert.Equal(t, want, got) }) } func TestConvertLevel(t *testing.T) { tests := []struct { level zapcore.Level expectedSev log.Severity }{ {zapcore.DebugLevel, log.SeverityDebug}, {zapcore.InfoLevel, log.SeverityInfo}, {zapcore.WarnLevel, log.SeverityWarn}, {zapcore.ErrorLevel, log.SeverityError}, {zapcore.DPanicLevel, log.SeverityFatal1}, {zapcore.PanicLevel, log.SeverityFatal2}, {zapcore.FatalLevel, log.SeverityFatal3}, {zapcore.InvalidLevel, log.SeverityUndefined}, } for _, test := range tests { result := convertLevel(test.level) if result != test.expectedSev { t.Errorf("For level %v, expected %v but got %v", test.level, test.expectedSev, result) } } } func BenchmarkCoreWrite(b *testing.B) { benchmarks := []struct { name string fields []zapcore.Field }{ { name: "10 fields", fields: []zapcore.Field{ zap.Int16("a", 1), zap.String("k", "a"), zap.Bool("k", true), zap.Time("k", time.Unix(1000, 1000)), zap.Binary("k", []byte{1, 2}), zap.ByteString("k", []byte{1, 2}), zap.Object("k", loggable{true}), zap.Array("k", loggable{true}), zap.String("k", "a"), zap.Ints("k", []int{1, 2}), }, }, { name: "20 fields", fields: []zapcore.Field{ zap.Int16("a", 1), zap.String("k", "a"), zap.Bool("k", true), zap.Time("k", time.Unix(1000, 1000)), zap.Binary("k", []byte{1, 2}), zap.ByteString("k", []byte{1, 2}), zap.Object("k", loggable{true}), zap.String("k", "a"), zap.Array("k", loggable{true}), zap.Ints("k", []int{1, 2}), zap.Int16("a", 1), zap.String("k", "a"), zap.Bool("k", true), zap.Time("k", time.Unix(1000, 1000)), zap.Binary("k", []byte{1, 2}), zap.ByteString("k", []byte{1, 2}), zap.Object("k", loggable{true}), zap.Array("k", loggable{true}), zap.String("k", "a"), zap.Ints("k", []int{1, 2}), }, }, { // Benchmark with nested namespace name: "Namespace", fields: []zapcore.Field{ zap.Namespace("a"), zap.Int16("a", 1), zap.String("k", "a"), zap.Bool("k", true), zap.Time("k", time.Unix(1000, 1000)), zap.Binary("k", []byte{1, 2}), zap.Namespace("b"), zap.Binary("k", []byte{1, 2}), zap.Object("k", loggable{true}), zap.String("k", "a"), zap.Array("k", loggable{true}), zap.Ints("k", []int{1, 2}), }, }, } for _, bm := range benchmarks { b.Run(bm.name, func(b *testing.B) { zc := NewCore(loggerName) b.ReportAllocs() b.RunParallel(func(pb *testing.PB) { for pb.Next() { err := zc.Write(testEntry, bm.fields) if err != nil { b.Errorf("Unexpected error: %v", err) } } }) }) } for _, bm := range benchmarks { b.Run(fmt.Sprint("With", bm.name), func(b *testing.B) { zc := NewCore(loggerName) zc1 := zc.With(bm.fields) b.ReportAllocs() b.RunParallel(func(pb *testing.PB) { for pb.Next() { err := zc1.Write(testEntry, []zapcore.Field{}) if err != nil { b.Errorf("Unexpected error: %v", err) } } }) }) } } open-telemetry-opentelemetry-go-contrib-e5abccb/bridges/otelzap/encoder.go000066400000000000000000000214111470323427300273130ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelzap // import "go.opentelemetry.io/contrib/bridges/otelzap" import ( "fmt" "reflect" "time" "go.uber.org/zap/zapcore" "go.opentelemetry.io/otel/log" ) var ( _ zapcore.ObjectEncoder = (*objectEncoder)(nil) _ zapcore.ArrayEncoder = (*arrayEncoder)(nil) ) type namespace struct { name string attrs []log.KeyValue next *namespace } // objectEncoder implements zapcore.ObjectEncoder. // It encodes given fields to OTel key-values. type objectEncoder struct { // root is a pointer to the default namespace root *namespace // cur is a pointer to the namespace we're currently writing to. cur *namespace } func newObjectEncoder(len int) *objectEncoder { keyval := make([]log.KeyValue, 0, len) m := &namespace{ attrs: keyval, } return &objectEncoder{ root: m, cur: m, } } // It iterates to the end of the linked list and appends namespace data. // Run this function before accessing complete result. func (m *objectEncoder) calculate(o *namespace) { if o.next == nil { return } m.calculate(o.next) o.attrs = append(o.attrs, log.Map(o.next.name, o.next.attrs...)) } func (m *objectEncoder) AddArray(key string, v zapcore.ArrayMarshaler) error { arr := newArrayEncoder() err := v.MarshalLogArray(arr) m.cur.attrs = append(m.cur.attrs, log.Slice(key, arr.elems...)) return err } func (m *objectEncoder) AddObject(k string, v zapcore.ObjectMarshaler) error { // Similar to console_encoder which uses capacity of 2: // https://github.com/uber-go/zap/blob/bd0cf0447951b77aa98dcfc1ac19e6f58d3ee64f/zapcore/console_encoder.go#L33. newobj := newObjectEncoder(2) err := v.MarshalLogObject(newobj) newobj.calculate(newobj.root) m.cur.attrs = append(m.cur.attrs, log.Map(k, newobj.root.attrs...)) return err } func (m *objectEncoder) AddBinary(k string, v []byte) { m.cur.attrs = append(m.cur.attrs, log.Bytes(k, v)) } func (m *objectEncoder) AddByteString(k string, v []byte) { m.cur.attrs = append(m.cur.attrs, log.String(k, string(v))) } func (m *objectEncoder) AddBool(k string, v bool) { m.cur.attrs = append(m.cur.attrs, log.Bool(k, v)) } func (m *objectEncoder) AddDuration(k string, v time.Duration) { m.AddInt64(k, v.Nanoseconds()) } func (m *objectEncoder) AddComplex128(k string, v complex128) { r := log.Float64("r", real(v)) i := log.Float64("i", imag(v)) m.cur.attrs = append(m.cur.attrs, log.Map(k, r, i)) } func (m *objectEncoder) AddFloat64(k string, v float64) { m.cur.attrs = append(m.cur.attrs, log.Float64(k, v)) } func (m *objectEncoder) AddInt64(k string, v int64) { m.cur.attrs = append(m.cur.attrs, log.Int64(k, v)) } func (m *objectEncoder) AddInt(k string, v int) { m.cur.attrs = append(m.cur.attrs, log.Int(k, v)) } func (m *objectEncoder) AddString(k string, v string) { m.cur.attrs = append(m.cur.attrs, log.String(k, v)) } func (m *objectEncoder) AddUint64(k string, v uint64) { m.cur.attrs = append(m.cur.attrs, log.KeyValue{ Key: k, Value: assignUintValue(v), }) } func (m *objectEncoder) AddReflected(k string, v interface{}) error { m.cur.attrs = append(m.cur.attrs, log.KeyValue{ Key: k, Value: convertValue(v), }) return nil } // OpenNamespace opens an isolated namespace where all subsequent fields will // be added. func (m *objectEncoder) OpenNamespace(k string) { keyValue := make([]log.KeyValue, 0, 5) s := &namespace{ name: k, attrs: keyValue, } m.cur.next = s m.cur = s } func (m *objectEncoder) AddComplex64(k string, v complex64) { m.AddComplex128(k, complex128(v)) } func (m *objectEncoder) AddTime(k string, v time.Time) { m.AddInt64(k, v.UnixNano()) } func (m *objectEncoder) AddFloat32(k string, v float32) { m.AddFloat64(k, float64(v)) } func (m *objectEncoder) AddInt32(k string, v int32) { m.AddInt64(k, int64(v)) } func (m *objectEncoder) AddInt16(k string, v int16) { m.AddInt64(k, int64(v)) } func (m *objectEncoder) AddInt8(k string, v int8) { m.AddInt64(k, int64(v)) } func (m *objectEncoder) AddUint(k string, v uint) { m.AddUint64(k, uint64(v)) } func (m *objectEncoder) AddUint32(k string, v uint32) { m.AddInt64(k, int64(v)) } func (m *objectEncoder) AddUint16(k string, v uint16) { m.AddInt64(k, int64(v)) } func (m *objectEncoder) AddUint8(k string, v uint8) { m.AddInt64(k, int64(v)) } func (m *objectEncoder) AddUintptr(k string, v uintptr) { m.AddUint64(k, uint64(v)) } func assignUintValue(v uint64) log.Value { const maxInt64 = ^uint64(0) >> 1 if v > maxInt64 { return log.Float64Value(float64(v)) } return log.Int64Value(int64(v)) // nolint:gosec // Overflow checked above. } // arrayEncoder implements [zapcore.ArrayEncoder]. type arrayEncoder struct { elems []log.Value } func newArrayEncoder() *arrayEncoder { return &arrayEncoder{ // Similar to console_encoder which uses capacity of 2: // https://github.com/uber-go/zap/blob/bd0cf0447951b77aa98dcfc1ac19e6f58d3ee64f/zapcore/console_encoder.go#L33. elems: make([]log.Value, 0, 2), } } func (a *arrayEncoder) AppendArray(v zapcore.ArrayMarshaler) error { arr := newArrayEncoder() err := v.MarshalLogArray(arr) a.elems = append(a.elems, log.SliceValue(arr.elems...)) return err } func (a *arrayEncoder) AppendObject(v zapcore.ObjectMarshaler) error { // Similar to console_encoder which uses capacity of 2: // https://github.com/uber-go/zap/blob/bd0cf0447951b77aa98dcfc1ac19e6f58d3ee64f/zapcore/console_encoder.go#L33. m := newObjectEncoder(2) err := v.MarshalLogObject(m) m.calculate(m.root) a.elems = append(a.elems, log.MapValue(m.root.attrs...)) return err } func (a *arrayEncoder) AppendReflected(v interface{}) error { a.elems = append(a.elems, convertValue(v)) return nil } func (a *arrayEncoder) AppendByteString(v []byte) { a.elems = append(a.elems, log.StringValue(string(v))) } func (a *arrayEncoder) AppendBool(v bool) { a.elems = append(a.elems, log.BoolValue(v)) } func (a *arrayEncoder) AppendFloat64(v float64) { a.elems = append(a.elems, log.Float64Value(v)) } func (a *arrayEncoder) AppendFloat32(v float32) { a.AppendFloat64(float64(v)) } func (a *arrayEncoder) AppendInt(v int) { a.elems = append(a.elems, log.IntValue(v)) } func (a *arrayEncoder) AppendInt64(v int64) { a.elems = append(a.elems, log.Int64Value(v)) } func (a *arrayEncoder) AppendString(v string) { a.elems = append(a.elems, log.StringValue(v)) } func (a *arrayEncoder) AppendComplex128(v complex128) { r := log.Float64("r", real(v)) i := log.Float64("i", imag(v)) a.elems = append(a.elems, log.MapValue(r, i)) } func (a *arrayEncoder) AppendUint64(v uint64) { a.elems = append(a.elems, assignUintValue(v)) } func (a *arrayEncoder) AppendComplex64(v complex64) { a.AppendComplex128(complex128(v)) } func (a *arrayEncoder) AppendDuration(v time.Duration) { a.AppendInt64(v.Nanoseconds()) } func (a *arrayEncoder) AppendInt32(v int32) { a.AppendInt64(int64(v)) } func (a *arrayEncoder) AppendInt16(v int16) { a.AppendInt64(int64(v)) } func (a *arrayEncoder) AppendInt8(v int8) { a.AppendInt64(int64(v)) } func (a *arrayEncoder) AppendTime(v time.Time) { a.AppendInt64(v.UnixNano()) } func (a *arrayEncoder) AppendUint(v uint) { a.AppendUint64(uint64(v)) } func (a *arrayEncoder) AppendUint32(v uint32) { a.AppendInt64(int64(v)) } func (a *arrayEncoder) AppendUint16(v uint16) { a.AppendInt64(int64(v)) } func (a *arrayEncoder) AppendUint8(v uint8) { a.AppendInt64(int64(v)) } func (a *arrayEncoder) AppendUintptr(v uintptr) { a.AppendUint64(uint64(v)) } func convertValue(v interface{}) log.Value { switch v := v.(type) { case bool: return log.BoolValue(v) case []byte: return log.BytesValue(v) case float64: return log.Float64Value(v) case int: return log.IntValue(v) case int64: return log.Int64Value(v) case string: return log.StringValue(v) } t := reflect.TypeOf(v) if t == nil { return log.Value{} } val := reflect.ValueOf(v) switch t.Kind() { case reflect.Struct: return log.StringValue(fmt.Sprintf("%+v", v)) case reflect.Slice, reflect.Array: items := make([]log.Value, 0, val.Len()) for i := 0; i < val.Len(); i++ { items = append(items, convertValue(val.Index(i).Interface())) } return log.SliceValue(items...) case reflect.Map: kvs := make([]log.KeyValue, 0, val.Len()) for _, k := range val.MapKeys() { var key string // If the key is a struct, use %+v to print the struct fields. if k.Kind() == reflect.Struct { key = fmt.Sprintf("%+v", k.Interface()) } else { key = fmt.Sprintf("%v", k.Interface()) } kvs = append(kvs, log.KeyValue{ Key: key, Value: convertValue(val.MapIndex(k).Interface()), }) } return log.MapValue(kvs...) case reflect.Ptr, reflect.Interface: return convertValue(val.Elem().Interface()) } return log.StringValue(fmt.Sprintf("unhandled attribute type: (%s) %+v", t, v)) } open-telemetry-opentelemetry-go-contrib-e5abccb/bridges/otelzap/encoder_test.go000066400000000000000000000304711470323427300303600ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Copyright (c) 2016-2017 Uber Technologies, Inc. package otelzap import ( "errors" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap/zapcore" "go.opentelemetry.io/otel/log" ) // Copied from https://github.com/uber-go/zap/blob/b39f8b6b6a44d8371a87610be50cce58eeeaabcb/zapcore/memory_encoder_test.go. func TestObjectEncoder(t *testing.T) { // Expected output of a turducken. wantTurducken := map[string]interface{}{ "ducks": []interface{}{ map[string]interface{}{"in": "chicken"}, map[string]interface{}{"in": "chicken"}, }, } tests := []struct { desc string f func(zapcore.ObjectEncoder) expected interface{} }{ { desc: "AddObject", f: func(e zapcore.ObjectEncoder) { assert.NoError(t, e.AddObject("k", loggable{true}), "Expected AddObject to succeed.") }, expected: map[string]interface{}{"loggable": "yes"}, }, { desc: "AddObject (nested)", f: func(e zapcore.ObjectEncoder) { assert.NoError(t, e.AddObject("k", turducken{}), "Expected AddObject to succeed.") }, expected: wantTurducken, }, { desc: "AddArray", f: func(e zapcore.ObjectEncoder) { assert.NoError(t, e.AddArray("k", zapcore.ArrayMarshalerFunc(func(arr zapcore.ArrayEncoder) error { arr.AppendBool(true) arr.AppendBool(false) arr.AppendBool(true) return nil })), "Expected AddArray to succeed.") }, expected: []interface{}{true, false, true}, }, { desc: "AddArray (nested)", f: func(e zapcore.ObjectEncoder) { assert.NoError(t, e.AddArray("k", turduckens(2)), "Expected AddArray to succeed.") }, expected: []interface{}{wantTurducken, wantTurducken}, }, { desc: "AddReflected", f: func(e zapcore.ObjectEncoder) { assert.NoError(t, e.AddReflected("k", map[string]interface{}{"foo": 5}), "Expected AddReflected to succeed.") }, expected: map[string]interface{}{"foo": int64(5)}, }, { desc: "AddBinary", f: func(e zapcore.ObjectEncoder) { e.AddBinary("k", []byte("foo")) }, expected: []byte("foo"), }, { desc: "AddByteString", f: func(e zapcore.ObjectEncoder) { e.AddByteString("k", []byte("foo")) }, expected: "foo", }, { desc: "AddBool", f: func(e zapcore.ObjectEncoder) { e.AddBool("k", true) }, expected: true, }, { desc: "AddFloat64", f: func(e zapcore.ObjectEncoder) { e.AddFloat64("k", 3.14) }, expected: 3.14, }, { desc: "AddFloat32", f: func(e zapcore.ObjectEncoder) { e.AddFloat32("k", 3.14) }, expected: float64(float32(3.14)), }, { desc: "AddInt", f: func(e zapcore.ObjectEncoder) { e.AddInt("k", 42) }, expected: int64(42), }, { desc: "AddInt64", f: func(e zapcore.ObjectEncoder) { e.AddInt64("k", 42) }, expected: int64(42), }, { desc: "AddInt32", f: func(e zapcore.ObjectEncoder) { e.AddInt32("k", 42) }, expected: int64(42), }, { desc: "AddInt16", f: func(e zapcore.ObjectEncoder) { e.AddInt16("k", 42) }, expected: int64(42), }, { desc: "AddInt8", f: func(e zapcore.ObjectEncoder) { e.AddInt8("k", 42) }, expected: int64(42), }, { desc: "AddString", f: func(e zapcore.ObjectEncoder) { e.AddString("k", "v") }, expected: "v", }, { desc: "AddUint64", f: func(e zapcore.ObjectEncoder) { e.AddUint64("k", 42) }, expected: int64(42), }, { desc: "AddUint64-Overflow", f: func(e zapcore.ObjectEncoder) { e.AddUint64("k", ^uint64(0)) }, expected: float64(^uint64(0)), }, { desc: "AddUint", f: func(e zapcore.ObjectEncoder) { e.AddUint("k", 42) }, expected: int64(42), }, { desc: "AddUint32", f: func(e zapcore.ObjectEncoder) { e.AddUint32("k", 42) }, expected: int64(42), }, { desc: "AddUint16", f: func(e zapcore.ObjectEncoder) { e.AddUint16("k", 42) }, expected: int64(42), }, { desc: "AddUint8", f: func(e zapcore.ObjectEncoder) { e.AddUint8("k", 42) }, expected: int64(42), }, { desc: "AddUintptr", f: func(e zapcore.ObjectEncoder) { e.AddUintptr("k", 42) }, expected: int64(42), }, { desc: "AddDuration", f: func(e zapcore.ObjectEncoder) { e.AddDuration("k", time.Millisecond) }, expected: int64(1000000), }, { desc: "AddTime", f: func(e zapcore.ObjectEncoder) { e.AddTime("k", time.Unix(0, 100)) }, expected: time.Unix(0, 100).UnixNano(), }, { desc: "AddComplex128", f: func(e zapcore.ObjectEncoder) { e.AddComplex128("k", 1+2i) }, expected: map[string]interface{}{"i": float64(2), "r": float64(1)}, }, { desc: "AddComplex64", f: func(e zapcore.ObjectEncoder) { e.AddComplex64("k", 1+2i) }, expected: map[string]interface{}{"i": float64(2), "r": float64(1)}, }, { desc: "OpenNamespace", f: func(e zapcore.ObjectEncoder) { e.OpenNamespace("k") e.AddInt("foo", 1) e.OpenNamespace("middle") e.AddInt("foo", 2) e.OpenNamespace("inner") e.AddInt("foo", 3) }, expected: map[string]interface{}{ "foo": int64(1), "middle": map[string]interface{}{ "foo": int64(2), "inner": map[string]interface{}{ "foo": int64(3), }, }, }, }, { desc: "object (with nested namespace) then string", f: func(e zapcore.ObjectEncoder) { e.OpenNamespace("k") assert.NoError(t, e.AddObject("obj", maybeNamespace{true})) e.AddString("not-obj", "should-be-outside-obj") }, expected: map[string]interface{}{ "obj": map[string]interface{}{ "obj-out": "obj-outside-namespace", "obj-namespace": map[string]interface{}{ "obj-in": "obj-inside-namespace", }, }, "not-obj": "should-be-outside-obj", }, }, } for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { enc := newObjectEncoder(1) tt.f(enc) enc.calculate(enc.root) require.Len(t, enc.root.attrs, 1) assert.Equal(t, tt.expected, value2Result((enc.root.attrs[0].Value)), "Unexpected encoder output.") }) } } // Copied from https://github.com/uber-go/zap/blob/b39f8b6b6a44d8371a87610be50cce58eeeaabcb/zapcore/memory_encoder_test.go. func TestArrayEncoder(t *testing.T) { tests := []struct { desc string f func(zapcore.ArrayEncoder) expected interface{} }{ // AppendObject is covered by AddObject (nested) case above. { desc: "AppendArray (arrays of arrays)", f: func(e zapcore.ArrayEncoder) { err := e.AppendArray(zapcore.ArrayMarshalerFunc(func(inner zapcore.ArrayEncoder) error { inner.AppendBool(true) inner.AppendBool(false) return nil })) assert.NoError(t, err) }, expected: []interface{}{true, false}, }, { desc: "AppendReflected", f: func(e zapcore.ArrayEncoder) { assert.NoError(t, e.AppendReflected(map[string]interface{}{"foo": 5})) }, expected: map[string]interface{}{"foo": int64(5)}, }, { desc: "object (no nested namespace) then string", f: func(e zapcore.ArrayEncoder) { err := e.AppendArray(zapcore.ArrayMarshalerFunc(func(inner zapcore.ArrayEncoder) error { err := inner.AppendObject(maybeNamespace{false}) inner.AppendString("should-be-outside-obj") return err })) assert.NoError(t, err) }, expected: []interface{}{ map[string]interface{}{ "obj-out": "obj-outside-namespace", }, "should-be-outside-obj", }, }, { desc: "object (with nested namespace) then string", f: func(e zapcore.ArrayEncoder) { err := e.AppendArray(zapcore.ArrayMarshalerFunc(func(inner zapcore.ArrayEncoder) error { err := inner.AppendObject(maybeNamespace{true}) inner.AppendString("should-be-outside-obj") return err })) assert.NoError(t, err) }, expected: []interface{}{ map[string]interface{}{ "obj-out": "obj-outside-namespace", "obj-namespace": map[string]interface{}{ "obj-in": "obj-inside-namespace", }, }, "should-be-outside-obj", }, }, {"AppendBool", func(e zapcore.ArrayEncoder) { e.AppendBool(true) }, true}, {"AppendByteString", func(e zapcore.ArrayEncoder) { e.AppendByteString([]byte("foo")) }, "foo"}, {"AppendFloat64", func(e zapcore.ArrayEncoder) { e.AppendFloat64(3.14) }, 3.14}, {"AppendFloat32", func(e zapcore.ArrayEncoder) { e.AppendFloat32(3.14) }, float64(float32(3.14))}, {"AppendInt64", func(e zapcore.ArrayEncoder) { e.AppendInt64(42) }, int64(42)}, {"AppendInt32", func(e zapcore.ArrayEncoder) { e.AppendInt32(42) }, int64(42)}, {"AppendInt16", func(e zapcore.ArrayEncoder) { e.AppendInt16(42) }, int64(42)}, {"AppendInt8", func(e zapcore.ArrayEncoder) { e.AppendInt8(42) }, int64(42)}, {"AppendInt", func(e zapcore.ArrayEncoder) { e.AppendInt(42) }, int64(42)}, {"AppendString", func(e zapcore.ArrayEncoder) { e.AppendString("foo") }, "foo"}, {"AppendComplex128", func(e zapcore.ArrayEncoder) { e.AppendComplex128(1 + 2i) }, map[string]interface{}{"i": float64(2), "r": float64(1)}}, {"AppendComplex64", func(e zapcore.ArrayEncoder) { e.AppendComplex64(1 + 2i) }, map[string]interface{}{"i": float64(2), "r": float64(1)}}, {"AppendDuration", func(e zapcore.ArrayEncoder) { e.AppendDuration(time.Second) }, int64(1000000000)}, {"AppendTime", func(e zapcore.ArrayEncoder) { e.AppendTime(time.Unix(0, 100)) }, time.Unix(0, 100).UnixNano()}, {"AppendUint", func(e zapcore.ArrayEncoder) { e.AppendUint(42) }, int64(42)}, {"AppendUint64", func(e zapcore.ArrayEncoder) { e.AppendUint64(42) }, int64(42)}, {"AppendUint64 - overflow", func(e zapcore.ArrayEncoder) { e.AppendUint64(^uint64(0)) }, float64(^uint64(0))}, {"AppendUint32", func(e zapcore.ArrayEncoder) { e.AppendUint32(42) }, int64(42)}, {"AppendUint16", func(e zapcore.ArrayEncoder) { e.AppendUint16(42) }, int64(42)}, {"AppendUint8", func(e zapcore.ArrayEncoder) { e.AppendUint8(42) }, int64(42)}, {"AppendUintptr", func(e zapcore.ArrayEncoder) { e.AppendUintptr(42) }, int64(42)}, } for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { enc := newObjectEncoder(1) assert.NoError(t, enc.AddArray("k", zapcore.ArrayMarshalerFunc(func(arr zapcore.ArrayEncoder) error { tt.f(arr) tt.f(arr) return nil })), "Expected AddArray to succeed.") enc.calculate(enc.root) assert.Equal(t, []interface{}{tt.expected, tt.expected}, value2Result(enc.root.attrs[0].Value), "Unexpected encoder output.") }) } } type turducken struct{} func (t turducken) MarshalLogObject(enc zapcore.ObjectEncoder) error { return enc.AddArray("ducks", zapcore.ArrayMarshalerFunc(func(arr zapcore.ArrayEncoder) error { for i := 0; i < 2; i++ { err := arr.AppendObject(zapcore.ObjectMarshalerFunc(func(inner zapcore.ObjectEncoder) error { inner.AddString("in", "chicken") return nil })) if err != nil { return err } } return nil })) } type turduckens int func (t turduckens) MarshalLogArray(enc zapcore.ArrayEncoder) error { var err error tur := turducken{} for i := 0; i < int(t); i++ { err = errors.Join(err, enc.AppendObject(tur)) } return err } type loggable struct{ bool } func (l loggable) MarshalLogObject(enc zapcore.ObjectEncoder) error { if !l.bool { return errors.New("can't marshal") } enc.AddString("loggable", "yes") return nil } func (l loggable) MarshalLogArray(enc zapcore.ArrayEncoder) error { if !l.bool { return errors.New("can't marshal") } enc.AppendBool(true) return nil } // maybeNamespace is an ObjectMarshaler that sometimes opens a namespace. type maybeNamespace struct{ bool } func (m maybeNamespace) MarshalLogObject(enc zapcore.ObjectEncoder) error { enc.AddString("obj-out", "obj-outside-namespace") if m.bool { enc.OpenNamespace("obj-namespace") enc.AddString("obj-in", "obj-inside-namespace") } return nil } func value2Result(v log.Value) any { switch v.Kind() { case log.KindBool: return v.AsBool() case log.KindFloat64: return v.AsFloat64() case log.KindInt64: return v.AsInt64() case log.KindString: return v.AsString() case log.KindBytes: return v.AsBytes() case log.KindSlice: var s []any for _, val := range v.AsSlice() { s = append(s, value2Result(val)) } return s case log.KindMap: m := make(map[string]any) for _, val := range v.AsMap() { m[val.Key] = value2Result(val.Value) } return m } return nil } open-telemetry-opentelemetry-go-contrib-e5abccb/bridges/otelzap/example_test.go000066400000000000000000000032311470323427300303660ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelzap_test import ( "context" "os" "go.opentelemetry.io/contrib/bridges/otelzap" "go.opentelemetry.io/otel/log/noop" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) func Example() { // Use a working LoggerProvider implementation instead e.g. use go.opentelemetry.io/otel/sdk/log. provider := noop.NewLoggerProvider() // Initialize a zap logger with the otelzap bridge core. // This method actually doesn't log anything on your STDOUT, as everything // is shipped to a configured otel endpoint. logger := zap.New(otelzap.NewCore("my/pkg/name", otelzap.WithLoggerProvider(provider))) // You can now use your logger in your code. logger.Info("something really cool") // You can set context for trace correlation using zap.Any or zap.Reflect ctx := context.Background() logger.Info("setting context", zap.Any("context", ctx)) } func Example_multiple() { // Use a working LoggerProvider implementation instead e.g. use go.opentelemetry.io/otel/sdk/log. provider := noop.NewLoggerProvider() // If you want to log also on stdout, you can initialize a new zap.Core // that has multiple outputs using the method zap.NewTee(). With the following code, // logs will be written to stdout and also exported to the OTEL endpoint through the bridge. core := zapcore.NewTee( zapcore.NewCore(zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()), zapcore.AddSync(os.Stdout), zapcore.InfoLevel), otelzap.NewCore("my/pkg/name", otelzap.WithLoggerProvider(provider)), ) logger := zap.New(core) // You can now use your logger in your code. logger.Info("something really cool") } open-telemetry-opentelemetry-go-contrib-e5abccb/bridges/otelzap/go.mod000066400000000000000000000011271470323427300264550ustar00rootroot00000000000000module go.opentelemetry.io/contrib/bridges/otelzap go 1.22 require ( github.com/stretchr/testify v1.9.0 go.opentelemetry.io/otel/log v0.7.0 go.uber.org/zap v1.27.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/otel v1.31.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect go.opentelemetry.io/otel/trace v1.31.0 // indirect go.uber.org/multierr v1.11.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) open-telemetry-opentelemetry-go-contrib-e5abccb/bridges/otelzap/go.sum000066400000000000000000000051171470323427300265050ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/log v0.7.0 h1:d1abJc0b1QQZADKvfe9JqqrfmPYQCz2tUSO+0XZmuV4= go.opentelemetry.io/otel/log v0.7.0/go.mod h1:2jf2z7uVfnzDNknKTO9G+ahcOAyWcp1fJmk/wJjULRo= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= open-telemetry-opentelemetry-go-contrib-e5abccb/bridges/prometheus/000077500000000000000000000000001470323427300260635ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/bridges/prometheus/BENCHMARKS.md000066400000000000000000000045101470323427300300220ustar00rootroot00000000000000## Summary Using the Prometheus bridge and the OTLP exporter adds roughly ~50% to the CPU and memory overhead of an application compared to serving a Prometheus HTTP endpoint for metrics. However, unless the application has extremely high cardinality for metrics, this is unlikely to represent a significant amount of additional overhead because the base-line memory consumption of client libraries is relatively low. For an application with 30k timeseries (which is a very high number), the additional overhead is about 50MB and about 0.1 CPU cores. The bridge is particularly useful if you are exporting to an OpenTelemetry Collector, since the OTLP receiver is much more efficient than the Prometheus receiver. For the same 30k timeseries, the Prometheus receiver uses 3x the amount of memory, and 20x the amount of CPU. In concrete numbers, this is an additional 228 MB of memory, and 0.57 CPU cores. For an application using the Prometheus client library, and exporting to an OpenTelemetry collector, the total CPU usage is 55% lower and total memory usage is 45% lower when using the bridge and the OTLP receiver compared to using a Prometheus endpoint and the collector's Prometheus receiver. ## Methods and Results The sample application uses the Prometheus client library, and defines one histogram with the default 12 buckets, one counter, and one gauge. Each metric has a single label with 10k values, which are observed every second. See the [sample application's source](https://github.com/dashpole/client_golang/pull/1). The memory usage of the sample application is measured using the `/memory/classes/total:bytes` metric from the go runtime. The CPU usage of the application is measured using `top`. The CPU and memory usage of the collector are measured using `docker stats`. It was built using v0.50.0 of the bridge, v1.25.0 of the OpenTelemetry API and SDK, and v1.19.0 of the Prometheus client. The OpenTelemetry Collector is configured with only the OTLP or Prometheus receiver, and the debug (logging) exporter with only the basic output. The benchmark uses the Contrib distribution at v0.97.0. | Experiment | Memory Usage (MB) | CPU Usage (millicores) | |---|---|---| | App w/ Prometheus Export | 94 | 220 | | App w/ Bridge + OTLP Export | 140 | 330 | | Collector w/ Prometheus Receiver | 320 | 600 | | Collector w/ OTLP Receiver | 92 | 30 | open-telemetry-opentelemetry-go-contrib-e5abccb/bridges/prometheus/config.go000066400000000000000000000021511470323427300276560ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package prometheus // import "go.opentelemetry.io/contrib/bridges/prometheus" import ( "github.com/prometheus/client_golang/prometheus" ) // config contains options for the producer. type config struct { gatherers []prometheus.Gatherer } // newConfig creates a validated config configured with options. func newConfig(opts ...Option) config { cfg := config{} for _, opt := range opts { cfg = opt.apply(cfg) } if len(cfg.gatherers) == 0 { cfg.gatherers = []prometheus.Gatherer{prometheus.DefaultGatherer} } return cfg } // Option sets producer option values. type Option interface { apply(config) config } type optionFunc func(config) config func (fn optionFunc) apply(cfg config) config { return fn(cfg) } // WithGatherer configures which prometheus Gatherer the Bridge will gather // from. If no registerer is used the prometheus DefaultGatherer is used. func WithGatherer(gatherer prometheus.Gatherer) Option { return optionFunc(func(cfg config) config { cfg.gatherers = append(cfg.gatherers, gatherer) return cfg }) } open-telemetry-opentelemetry-go-contrib-e5abccb/bridges/prometheus/config_test.go000066400000000000000000000022221470323427300307140ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package prometheus // import "go.opentelemetry.io/contrib/bridges/prometheus" import ( "testing" "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/assert" ) func TestNewConfig(t *testing.T) { otherRegistry := prometheus.NewRegistry() testCases := []struct { name string options []Option wantConfig config }{ { name: "Default", options: nil, wantConfig: config{ gatherers: []prometheus.Gatherer{prometheus.DefaultGatherer}, }, }, { name: "With a different gatherer", options: []Option{WithGatherer(otherRegistry)}, wantConfig: config{ gatherers: []prometheus.Gatherer{otherRegistry}, }, }, { name: "Multiple gatherers", options: []Option{WithGatherer(otherRegistry), WithGatherer(prometheus.DefaultGatherer)}, wantConfig: config{ gatherers: []prometheus.Gatherer{otherRegistry, prometheus.DefaultGatherer}, }, }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { cfg := newConfig(tt.options...) assert.Equal(t, tt.wantConfig, cfg) }) } } open-telemetry-opentelemetry-go-contrib-e5abccb/bridges/prometheus/doc.go000066400000000000000000000024211470323427300271560ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package prometheus provides a bridge from Prometheus to OpenTelemetry. // // The Prometheus Bridge allows using the [Prometheus Golang client library] // with the OpenTelemetry SDK. This enables prometheus instrumentation libraries // to be used with OpenTelemetry exporters, including OTLP. // // Prometheus histograms are translated to OpenTelemetry exponential histograms // when native histograms are enabled in the Prometheus client. To enable // Prometheus native histograms, set the (currently experimental) NativeHistogram... // options of the prometheus [HistogramOpts] when creating prometheus histograms. // // While the Prometheus Bridge has some overhead, it can significantly reduce the // combined overall CPU and Memory footprint when sending to an OpenTelemetry // Collector. See the [benchmarks] for more details. // // [Prometheus Golang client library]: https://github.com/prometheus/client_golang // [HistogramOpts]: https://pkg.go.dev/github.com/prometheus/client_golang/prometheus#HistogramOpts // [benchmarks]: https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/bridges/prometheus/BENCHMARKS.md package prometheus // import "go.opentelemetry.io/contrib/bridges/prometheus" open-telemetry-opentelemetry-go-contrib-e5abccb/bridges/prometheus/example_test.go000066400000000000000000000020351470323427300311040ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package prometheus_test import ( "go.opentelemetry.io/contrib/bridges/prometheus" "go.opentelemetry.io/otel/sdk/metric" ) func ExampleNewMetricProducer() { // Create a Promethes bridge "Metric Producer" which adds metrics from the // prometheus.DefaultGatherer. Add the WithGatherer option to add metrics // from other registries. bridge := prometheus.NewMetricProducer() // This reader is used as a stand-in for a reader that will actually export // data. See https://pkg.go.dev/go.opentelemetry.io/otel/exporters for // exporters that can be used as or with readers. The metric.WithProducer // option adds metrics from the Prometheus bridge to the reader. reader := metric.NewManualReader(metric.WithProducer(bridge)) // Create an OTel MeterProvider with our reader. Metrics from OpenTelemetry // instruments are combined with metrics from Prometheus instruments in // exported batches of metrics. _ = metric.NewMeterProvider(metric.WithReader(reader)) } open-telemetry-opentelemetry-go-contrib-e5abccb/bridges/prometheus/go.mod000066400000000000000000000021741470323427300271750ustar00rootroot00000000000000module go.opentelemetry.io/contrib/bridges/prometheus go 1.22 require ( github.com/prometheus/client_golang v1.20.4 github.com/prometheus/client_model v0.6.1 github.com/stretchr/testify v1.9.0 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/sdk v1.31.0 go.opentelemetry.io/otel/sdk/metric v1.31.0 ) require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/kr/text v0.2.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/common v0.60.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/rogpeppe/go-internal v1.13.1 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect go.opentelemetry.io/otel/trace v1.31.0 // indirect golang.org/x/sys v0.26.0 // indirect google.golang.org/protobuf v1.35.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) open-telemetry-opentelemetry-go-contrib-e5abccb/bridges/prometheus/go.sum000066400000000000000000000116411470323427300272210ustar00rootroot00000000000000github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI= github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.60.0 h1:+V9PAREWNvJMAuJ1x1BaWl9dewMW4YrHZQbx0sJNllA= github.com/prometheus/common v0.60.0/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc= go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= open-telemetry-opentelemetry-go-contrib-e5abccb/bridges/prometheus/producer.go000066400000000000000000000303671470323427300302460ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package prometheus // import "go.opentelemetry.io/contrib/bridges/prometheus" import ( "context" "errors" "fmt" "math" "strings" "time" "github.com/prometheus/client_golang/prometheus" dto "github.com/prometheus/client_model/go" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/instrumentation" "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" ) const ( scopeName = "go.opentelemetry.io/contrib/bridges/prometheus" traceIDLabel = "trace_id" spanIDLabel = "span_id" ) var ( errUnsupportedType = errors.New("unsupported metric type") processStartTime = time.Now() ) type producer struct { gatherers prometheus.Gatherers } // NewMetricProducer returns a metric.Producer that fetches metrics from // Prometheus. This can be used to allow Prometheus instrumentation to be // added to an OpenTelemetry export pipeline. func NewMetricProducer(opts ...Option) metric.Producer { cfg := newConfig(opts...) return &producer{ gatherers: cfg.gatherers, } } func (p *producer) Produce(context.Context) ([]metricdata.ScopeMetrics, error) { now := time.Now() var errs multierr otelMetrics := make([]metricdata.Metrics, 0) for _, gatherer := range p.gatherers { promMetrics, err := gatherer.Gather() if err != nil { errs = append(errs, err) continue } m, err := convertPrometheusMetricsInto(promMetrics, now) otelMetrics = append(otelMetrics, m...) if err != nil { errs = append(errs, err) } } if errs.errOrNil() != nil { otel.Handle(errs.errOrNil()) } if len(otelMetrics) == 0 { return nil, nil } return []metricdata.ScopeMetrics{{ Scope: instrumentation.Scope{ Name: scopeName, }, Metrics: otelMetrics, }}, nil } func convertPrometheusMetricsInto(promMetrics []*dto.MetricFamily, now time.Time) ([]metricdata.Metrics, error) { var errs multierr otelMetrics := make([]metricdata.Metrics, 0) for _, pm := range promMetrics { if len(pm.GetMetric()) == 0 { // This shouldn't ever happen continue } newMetric := metricdata.Metrics{ Name: pm.GetName(), Description: pm.GetHelp(), } switch pm.GetType() { case dto.MetricType_GAUGE: newMetric.Data = convertGauge(pm.GetMetric(), now) case dto.MetricType_COUNTER: newMetric.Data = convertCounter(pm.GetMetric(), now) case dto.MetricType_SUMMARY: newMetric.Data = convertSummary(pm.GetMetric(), now) case dto.MetricType_HISTOGRAM: if isExponentialHistogram(pm.GetMetric()[0].GetHistogram()) { newMetric.Data = convertExponentialHistogram(pm.GetMetric(), now) } else { newMetric.Data = convertHistogram(pm.GetMetric(), now) } default: // MetricType_GAUGE_HISTOGRAM, MetricType_UNTYPED errs = append(errs, fmt.Errorf("%w: %v for metric %v", errUnsupportedType, pm.GetType(), pm.GetName())) continue } otelMetrics = append(otelMetrics, newMetric) } return otelMetrics, errs.errOrNil() } func isExponentialHistogram(hist *dto.Histogram) bool { // The prometheus go client ensures at least one of these is non-zero // so it can be distinguished from a fixed-bucket histogram. // https://github.com/prometheus/client_golang/blob/7ac90362b02729a65109b33d172bafb65d7dab50/prometheus/histogram.go#L818 return hist.GetZeroThreshold() > 0 || hist.GetZeroCount() > 0 || len(hist.GetPositiveSpan()) > 0 || len(hist.GetNegativeSpan()) > 0 } func convertGauge(metrics []*dto.Metric, now time.Time) metricdata.Gauge[float64] { otelGauge := metricdata.Gauge[float64]{ DataPoints: make([]metricdata.DataPoint[float64], len(metrics)), } for i, m := range metrics { dp := metricdata.DataPoint[float64]{ Attributes: convertLabels(m.GetLabel()), Time: now, Value: m.GetGauge().GetValue(), } if m.GetTimestampMs() != 0 { dp.Time = time.UnixMilli(m.GetTimestampMs()) } otelGauge.DataPoints[i] = dp } return otelGauge } func convertCounter(metrics []*dto.Metric, now time.Time) metricdata.Sum[float64] { otelCounter := metricdata.Sum[float64]{ DataPoints: make([]metricdata.DataPoint[float64], len(metrics)), Temporality: metricdata.CumulativeTemporality, IsMonotonic: true, } for i, m := range metrics { dp := metricdata.DataPoint[float64]{ Attributes: convertLabels(m.GetLabel()), StartTime: processStartTime, Time: now, Value: m.GetCounter().GetValue(), } if ex := m.GetCounter().GetExemplar(); ex != nil { dp.Exemplars = []metricdata.Exemplar[float64]{convertExemplar(ex)} } createdTs := m.GetCounter().GetCreatedTimestamp() if createdTs.IsValid() { dp.StartTime = createdTs.AsTime() } if m.GetTimestampMs() != 0 { dp.Time = time.UnixMilli(m.GetTimestampMs()) } otelCounter.DataPoints[i] = dp } return otelCounter } func convertExponentialHistogram(metrics []*dto.Metric, now time.Time) metricdata.ExponentialHistogram[float64] { otelExpHistogram := metricdata.ExponentialHistogram[float64]{ DataPoints: make([]metricdata.ExponentialHistogramDataPoint[float64], len(metrics)), Temporality: metricdata.CumulativeTemporality, } for i, m := range metrics { dp := metricdata.ExponentialHistogramDataPoint[float64]{ Attributes: convertLabels(m.GetLabel()), StartTime: processStartTime, Time: now, Count: m.GetHistogram().GetSampleCount(), Sum: m.GetHistogram().GetSampleSum(), Scale: m.GetHistogram().GetSchema(), ZeroCount: m.GetHistogram().GetZeroCount(), ZeroThreshold: m.GetHistogram().GetZeroThreshold(), PositiveBucket: convertExponentialBuckets( m.GetHistogram().GetPositiveSpan(), m.GetHistogram().GetPositiveDelta(), ), NegativeBucket: convertExponentialBuckets( m.GetHistogram().GetNegativeSpan(), m.GetHistogram().GetNegativeDelta(), ), // TODO: Support exemplars } createdTs := m.GetHistogram().GetCreatedTimestamp() if createdTs.IsValid() { dp.StartTime = createdTs.AsTime() } if t := m.GetTimestampMs(); t != 0 { dp.Time = time.UnixMilli(t) } otelExpHistogram.DataPoints[i] = dp } return otelExpHistogram } func convertExponentialBuckets(bucketSpans []*dto.BucketSpan, deltas []int64) metricdata.ExponentialBucket { if len(bucketSpans) == 0 { return metricdata.ExponentialBucket{} } // Prometheus Native Histograms buckets are indexed by upper boundary // while Exponential Histograms are indexed by lower boundary, the result // being that the Offset fields are different-by-one. initialOffset := bucketSpans[0].GetOffset() - 1 // We will have one bucket count for each delta, and zeros for the offsets // after the initial offset. lenCounts := len(deltas) for i, bs := range bucketSpans { if i != 0 { lenCounts += int(bs.GetOffset()) } } counts := make([]uint64, lenCounts) deltaIndex := 0 countIndex := int32(0) count := int64(0) for i, bs := range bucketSpans { // Do not insert zeroes if this is the first bucketSpan, since those // zeroes are accounted for in the Offset field. if i != 0 { // Increase the count index by the Offset to insert Offset zeroes countIndex += bs.GetOffset() } for j := uint32(0); j < bs.GetLength(); j++ { // Convert deltas to the cumulative number of observations count += deltas[deltaIndex] deltaIndex++ // count should always be positive after accounting for deltas if count > 0 { counts[countIndex] = uint64(count) } countIndex++ } } return metricdata.ExponentialBucket{ Offset: initialOffset, Counts: counts, } } func convertHistogram(metrics []*dto.Metric, now time.Time) metricdata.Histogram[float64] { otelHistogram := metricdata.Histogram[float64]{ DataPoints: make([]metricdata.HistogramDataPoint[float64], len(metrics)), Temporality: metricdata.CumulativeTemporality, } for i, m := range metrics { bounds, bucketCounts, exemplars := convertBuckets(m.GetHistogram().GetBucket(), m.GetHistogram().GetSampleCount()) dp := metricdata.HistogramDataPoint[float64]{ Attributes: convertLabels(m.GetLabel()), StartTime: processStartTime, Time: now, Count: m.GetHistogram().GetSampleCount(), Sum: m.GetHistogram().GetSampleSum(), Bounds: bounds, BucketCounts: bucketCounts, Exemplars: exemplars, } createdTs := m.GetHistogram().GetCreatedTimestamp() if createdTs.IsValid() { dp.StartTime = createdTs.AsTime() } if m.GetTimestampMs() != 0 { dp.Time = time.UnixMilli(m.GetTimestampMs()) } otelHistogram.DataPoints[i] = dp } return otelHistogram } func convertBuckets(buckets []*dto.Bucket, sampleCount uint64) ([]float64, []uint64, []metricdata.Exemplar[float64]) { if len(buckets) == 0 { // This should never happen return nil, nil, nil } // buckets will only include the +Inf bucket if there is an exemplar for it // https://github.com/prometheus/client_golang/blob/d038ab96c0c7b9cd217a39072febd610bcdf1fd8/prometheus/metric.go#L189 // we need to handle the case where it is present, or where it is missing. hasInf := math.IsInf(buckets[len(buckets)-1].GetUpperBound(), +1) var bounds []float64 var bucketCounts []uint64 if hasInf { bounds = make([]float64, len(buckets)-1) bucketCounts = make([]uint64, len(buckets)) } else { bounds = make([]float64, len(buckets)) bucketCounts = make([]uint64, len(buckets)+1) } exemplars := make([]metricdata.Exemplar[float64], 0) for i, bucket := range buckets { // The last bound may be the +Inf bucket, which is implied in OTel, but // is explicit in Prometheus. Skip the last boundary if it is the +Inf // bound. if bound := bucket.GetUpperBound(); !math.IsInf(bound, +1) { bounds[i] = bound } bucketCounts[i] = bucket.GetCumulativeCount() if ex := bucket.GetExemplar(); ex != nil { exemplars = append(exemplars, convertExemplar(ex)) } } if !hasInf { // The Inf bucket was missing, so set the last bucket counts to the // overall count bucketCounts[len(bucketCounts)-1] = sampleCount } return bounds, bucketCounts, exemplars } func convertSummary(metrics []*dto.Metric, now time.Time) metricdata.Summary { otelSummary := metricdata.Summary{ DataPoints: make([]metricdata.SummaryDataPoint, len(metrics)), } for i, m := range metrics { dp := metricdata.SummaryDataPoint{ Attributes: convertLabels(m.GetLabel()), StartTime: processStartTime, Time: now, Count: m.GetSummary().GetSampleCount(), Sum: m.GetSummary().GetSampleSum(), QuantileValues: convertQuantiles(m.GetSummary().GetQuantile()), } createdTs := m.GetSummary().GetCreatedTimestamp() if createdTs.IsValid() { dp.StartTime = createdTs.AsTime() } if t := m.GetTimestampMs(); t != 0 { dp.Time = time.UnixMilli(t) } otelSummary.DataPoints[i] = dp } return otelSummary } func convertQuantiles(quantiles []*dto.Quantile) []metricdata.QuantileValue { otelQuantiles := make([]metricdata.QuantileValue, len(quantiles)) for i, quantile := range quantiles { dp := metricdata.QuantileValue{ Quantile: quantile.GetQuantile(), Value: quantile.GetValue(), } otelQuantiles[i] = dp } return otelQuantiles } func convertLabels(labels []*dto.LabelPair) attribute.Set { kvs := make([]attribute.KeyValue, len(labels)) for i, l := range labels { kvs[i] = attribute.String(l.GetName(), l.GetValue()) } return attribute.NewSet(kvs...) } func convertExemplar(exemplar *dto.Exemplar) metricdata.Exemplar[float64] { attrs := make([]attribute.KeyValue, 0) var traceID, spanID []byte // find the trace ID and span ID in attributes, if it exists for _, label := range exemplar.GetLabel() { if label.GetName() == traceIDLabel { traceID = []byte(label.GetValue()) } else if label.GetName() == spanIDLabel { spanID = []byte(label.GetValue()) } else { attrs = append(attrs, attribute.String(label.GetName(), label.GetValue())) } } return metricdata.Exemplar[float64]{ Value: exemplar.GetValue(), Time: exemplar.GetTimestamp().AsTime(), TraceID: traceID, SpanID: spanID, FilteredAttributes: attrs, } } type multierr []error func (e multierr) errOrNil() error { if len(e) == 0 { return nil } else if len(e) == 1 { return e[0] } return e } func (e multierr) Error() string { es := make([]string, len(e)) for i, err := range e { es[i] = fmt.Sprintf("* %s", err) } return strings.Join(es, "\n\t") } open-telemetry-opentelemetry-go-contrib-e5abccb/bridges/prometheus/producer_test.go000066400000000000000000000406031470323427300312770ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package prometheus // import "go.opentelemetry.io/contrib/bridges/prometheus" import ( "context" "testing" "time" "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/instrumentation" "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest" ) const ( traceIDStr = "4bf92f3577b34da6a3ce929d0e0e4736" spanIDStr = "00f067aa0ba902b7" ) func TestProduce(t *testing.T) { testCases := []struct { name string testFn func(*prometheus.Registry) expected []metricdata.ScopeMetrics wantErr error }{ { name: "no metrics registered", testFn: func(*prometheus.Registry) {}, }, { name: "gauge", testFn: func(reg *prometheus.Registry) { metric := prometheus.NewGauge(prometheus.GaugeOpts{ Name: "test_gauge_metric", Help: "A gauge metric for testing", ConstLabels: prometheus.Labels(map[string]string{ "foo": "bar", }), }) reg.MustRegister(metric) metric.Set(123.4) }, expected: []metricdata.ScopeMetrics{{ Scope: instrumentation.Scope{ Name: scopeName, }, Metrics: []metricdata.Metrics{ { Name: "test_gauge_metric", Description: "A gauge metric for testing", Data: metricdata.Gauge[float64]{ DataPoints: []metricdata.DataPoint[float64]{ { Attributes: attribute.NewSet(attribute.String("foo", "bar")), Value: 123.4, }, }, }, }, }, }}, }, { name: "counter", testFn: func(reg *prometheus.Registry) { metric := prometheus.NewCounter(prometheus.CounterOpts{ Name: "test_counter_metric", Help: "A counter metric for testing", ConstLabels: prometheus.Labels(map[string]string{ "foo": "bar", }), }) reg.MustRegister(metric) metric.Add(245.3) }, expected: []metricdata.ScopeMetrics{{ Scope: instrumentation.Scope{ Name: scopeName, }, Metrics: []metricdata.Metrics{ { Name: "test_counter_metric", Description: "A counter metric for testing", Data: metricdata.Sum[float64]{ Temporality: metricdata.CumulativeTemporality, IsMonotonic: true, DataPoints: []metricdata.DataPoint[float64]{ { Attributes: attribute.NewSet(attribute.String("foo", "bar")), Value: 245.3, }, }, }, }, }, }}, }, { name: "counter with exemplar", testFn: func(reg *prometheus.Registry) { metric := prometheus.NewCounter(prometheus.CounterOpts{ Name: "test_counter_metric", Help: "A counter metric for testing", ConstLabels: prometheus.Labels(map[string]string{ "foo": "bar", }), }) reg.MustRegister(metric) metric.(prometheus.ExemplarAdder).AddWithExemplar( 245.3, prometheus.Labels{ "trace_id": traceIDStr, "span_id": spanIDStr, "other_attribute": "abcd", }, ) }, expected: []metricdata.ScopeMetrics{{ Scope: instrumentation.Scope{ Name: scopeName, }, Metrics: []metricdata.Metrics{ { Name: "test_counter_metric", Description: "A counter metric for testing", Data: metricdata.Sum[float64]{ Temporality: metricdata.CumulativeTemporality, IsMonotonic: true, DataPoints: []metricdata.DataPoint[float64]{ { Attributes: attribute.NewSet(attribute.String("foo", "bar")), Value: 245.3, Exemplars: []metricdata.Exemplar[float64]{ { Value: 245.3, TraceID: []byte(traceIDStr), SpanID: []byte(spanIDStr), FilteredAttributes: []attribute.KeyValue{attribute.String("other_attribute", "abcd")}, }, }, }, }, }, }, }, }}, }, { name: "summary", testFn: func(reg *prometheus.Registry) { metric := prometheus.NewSummary(prometheus.SummaryOpts{ Name: "test_summary_metric", Help: "A summary metric for testing", Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}, ConstLabels: prometheus.Labels(map[string]string{ "foo": "bar", }), }) reg.MustRegister(metric) metric.Observe(15.0) }, expected: []metricdata.ScopeMetrics{{ Scope: instrumentation.Scope{ Name: scopeName, }, Metrics: []metricdata.Metrics{ { Name: "test_summary_metric", Description: "A summary metric for testing", Data: metricdata.Summary{ DataPoints: []metricdata.SummaryDataPoint{ { Count: 1, Sum: 15.0, QuantileValues: []metricdata.QuantileValue{ {Quantile: 0.5, Value: 15}, {Quantile: 0.9, Value: 15}, {Quantile: 0.99, Value: 15}, }, Attributes: attribute.NewSet(attribute.String("foo", "bar")), }, }, }, }, }, }}, }, { name: "histogram", testFn: func(reg *prometheus.Registry) { metric := prometheus.NewHistogram(prometheus.HistogramOpts{ Name: "test_histogram_metric", Help: "A histogram metric for testing", ConstLabels: prometheus.Labels(map[string]string{ "foo": "bar", }), }) reg.MustRegister(metric) metric.Observe(578.3) }, expected: []metricdata.ScopeMetrics{{ Scope: instrumentation.Scope{ Name: scopeName, }, Metrics: []metricdata.Metrics{ { Name: "test_histogram_metric", Description: "A histogram metric for testing", Data: metricdata.Histogram[float64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[float64]{ { Count: 1, Sum: 578.3, Bounds: []float64{0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10}, BucketCounts: []uint64{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, Attributes: attribute.NewSet(attribute.String("foo", "bar")), Exemplars: []metricdata.Exemplar[float64]{}, }, }, }, }, }, }}, }, { name: "histogram with exemplar", testFn: func(reg *prometheus.Registry) { metric := prometheus.NewHistogram(prometheus.HistogramOpts{ Name: "test_histogram_metric_with_exemplar", Help: "A histogram metric for testing", ConstLabels: prometheus.Labels(map[string]string{ "foo": "bar", }), }) reg.MustRegister(metric) metric.(prometheus.ExemplarObserver).ObserveWithExemplar( 578.3, prometheus.Labels{ "trace_id": traceIDStr, "span_id": spanIDStr, "other_attribute": "efgh", }, ) }, expected: []metricdata.ScopeMetrics{{ Scope: instrumentation.Scope{ Name: scopeName, }, Metrics: []metricdata.Metrics{ { Name: "test_histogram_metric_with_exemplar", Description: "A histogram metric for testing", Data: metricdata.Histogram[float64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[float64]{ { Count: 1, Sum: 578.3, Bounds: []float64{0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10}, BucketCounts: []uint64{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, Attributes: attribute.NewSet(attribute.String("foo", "bar")), Exemplars: []metricdata.Exemplar[float64]{ { Value: 578.3, TraceID: []byte(traceIDStr), SpanID: []byte(spanIDStr), FilteredAttributes: []attribute.KeyValue{ attribute.String("other_attribute", "efgh"), }, }, }, }, }, }, }, }, }}, }, { name: "exponential histogram", testFn: func(reg *prometheus.Registry) { metric := prometheus.NewHistogram(prometheus.HistogramOpts{ Name: "test_exponential_histogram_metric", Help: "An exponential histogram metric for testing", // This enables collection of native histograms in the prometheus client. NativeHistogramBucketFactor: 1.5, ConstLabels: prometheus.Labels(map[string]string{ "foo": "bar", }), }) reg.MustRegister(metric) metric.Observe(78.3) metric.Observe(2.3) metric.Observe(2.3) metric.Observe(.5) metric.Observe(-78.3) metric.Observe(-.15) metric.Observe(0.0) }, expected: []metricdata.ScopeMetrics{{ Scope: instrumentation.Scope{ Name: scopeName, }, Metrics: []metricdata.Metrics{ { Name: "test_exponential_histogram_metric", Description: "An exponential histogram metric for testing", Data: metricdata.ExponentialHistogram[float64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.ExponentialHistogramDataPoint[float64]{ { Count: 7, Sum: 4.949999999999994, Scale: 1, ZeroCount: 1, PositiveBucket: metricdata.ExponentialBucket{ Offset: -3, Counts: []uint64{1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, }, NegativeBucket: metricdata.ExponentialBucket{ Offset: -6, Counts: []uint64{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, }, Attributes: attribute.NewSet(attribute.String("foo", "bar")), ZeroThreshold: prometheus.DefNativeHistogramZeroThreshold, }, }, }, }, }, }}, }, { name: "exponential histogram with only positive observations", testFn: func(reg *prometheus.Registry) { metric := prometheus.NewHistogram(prometheus.HistogramOpts{ Name: "test_exponential_histogram_metric", Help: "An exponential histogram metric for testing", // This enables collection of native histograms in the prometheus client. NativeHistogramBucketFactor: 1.5, ConstLabels: prometheus.Labels(map[string]string{ "foo": "bar", }), }) reg.MustRegister(metric) metric.Observe(78.3) metric.Observe(2.3) metric.Observe(2.3) metric.Observe(.5) metric.Observe(0.0) }, expected: []metricdata.ScopeMetrics{{ Scope: instrumentation.Scope{ Name: scopeName, }, Metrics: []metricdata.Metrics{ { Name: "test_exponential_histogram_metric", Description: "An exponential histogram metric for testing", Data: metricdata.ExponentialHistogram[float64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.ExponentialHistogramDataPoint[float64]{ { Count: 5, Sum: 83.39999999999999, Scale: 1, ZeroCount: 1, PositiveBucket: metricdata.ExponentialBucket{ Offset: -3, Counts: []uint64{1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, }, NegativeBucket: metricdata.ExponentialBucket{}, Attributes: attribute.NewSet(attribute.String("foo", "bar")), ZeroThreshold: prometheus.DefNativeHistogramZeroThreshold, }, }, }, }, }, }}, }, { name: "partial success", testFn: func(reg *prometheus.Registry) { metric := prometheus.NewGauge(prometheus.GaugeOpts{ Name: "test_gauge_metric", Help: "A gauge metric for testing", ConstLabels: prometheus.Labels(map[string]string{ "foo": "bar", }), }) reg.MustRegister(metric) metric.Set(123.4) unsupportedMetric := prometheus.NewUntypedFunc(prometheus.UntypedOpts{ Name: "test_untyped_metric", Help: "An untyped metric for testing", }, func() float64 { return 135.8 }) reg.MustRegister(unsupportedMetric) }, expected: []metricdata.ScopeMetrics{{ Scope: instrumentation.Scope{ Name: scopeName, }, Metrics: []metricdata.Metrics{ { Name: "test_gauge_metric", Description: "A gauge metric for testing", Data: metricdata.Gauge[float64]{ DataPoints: []metricdata.DataPoint[float64]{ { Attributes: attribute.NewSet(attribute.String("foo", "bar")), Value: 123.4, }, }, }, }, }, }}, wantErr: errUnsupportedType, }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { reg := prometheus.NewRegistry() tt.testFn(reg) p := NewMetricProducer(WithGatherer(reg)) output, err := p.Produce(context.Background()) if tt.wantErr == nil { assert.NoError(t, err) } require.Equal(t, len(output), len(tt.expected)) for i := range output { metricdatatest.AssertEqual(t, tt.expected[i], output[i], metricdatatest.IgnoreTimestamp()) } }) } } func TestProduceForStartTime(t *testing.T) { testCases := []struct { name string testFn func(*prometheus.Registry) startTimeFn func(metricdata.Aggregation) []time.Time }{ { name: "counter", testFn: func(reg *prometheus.Registry) { metric := prometheus.NewCounter(prometheus.CounterOpts{ Name: "test_counter_metric", Help: "A counter metric for testing", ConstLabels: prometheus.Labels(map[string]string{ "foo": "bar", }), }) reg.MustRegister(metric) metric.(prometheus.ExemplarAdder).AddWithExemplar( 245.3, prometheus.Labels{ "trace_id": traceIDStr, "span_id": spanIDStr, "other_attribute": "abcd", }, ) }, startTimeFn: func(aggr metricdata.Aggregation) []time.Time { dps := aggr.(metricdata.Sum[float64]).DataPoints sts := make([]time.Time, len(dps)) for i, dp := range dps { sts[i] = dp.StartTime } return sts }, }, { name: "histogram", testFn: func(reg *prometheus.Registry) { metric := prometheus.NewHistogram(prometheus.HistogramOpts{ Name: "test_histogram_metric", Help: "A histogram metric for testing", ConstLabels: prometheus.Labels(map[string]string{ "foo": "bar", }), }) reg.MustRegister(metric) metric.(prometheus.ExemplarObserver).ObserveWithExemplar( 578.3, prometheus.Labels{ "trace_id": traceIDStr, "span_id": spanIDStr, "other_attribute": "efgh", }, ) }, startTimeFn: func(aggr metricdata.Aggregation) []time.Time { dps := aggr.(metricdata.Histogram[float64]).DataPoints sts := make([]time.Time, len(dps)) for i, dp := range dps { sts[i] = dp.StartTime } return sts }, }, { name: "summary", testFn: func(reg *prometheus.Registry) { metric := prometheus.NewSummary(prometheus.SummaryOpts{ Name: "test_summary_metric", Help: "A summary metric for testing", ConstLabels: prometheus.Labels(map[string]string{ "foo": "bar", }), }) reg.MustRegister(metric) metric.Observe(78.3) }, startTimeFn: func(aggr metricdata.Aggregation) []time.Time { dps := aggr.(metricdata.Summary).DataPoints sts := make([]time.Time, len(dps)) for i, dp := range dps { sts[i] = dp.StartTime } return sts }, }, { name: "exponential histogram", testFn: func(reg *prometheus.Registry) { metric := prometheus.NewHistogram(prometheus.HistogramOpts{ Name: "test_exponential_histogram_metric", Help: "An exponential histogram metric for testing", // This enables collection of native histograms in the prometheus client. NativeHistogramBucketFactor: 1.5, ConstLabels: prometheus.Labels(map[string]string{ "foo": "bar", }), }) reg.MustRegister(metric) metric.Observe(78.3) }, startTimeFn: func(aggr metricdata.Aggregation) []time.Time { dps := aggr.(metricdata.ExponentialHistogram[float64]).DataPoints sts := make([]time.Time, len(dps)) for i, dp := range dps { sts[i] = dp.StartTime } return sts }, }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { reg := prometheus.NewRegistry() tt.testFn(reg) p := NewMetricProducer(WithGatherer(reg)) output, err := p.Produce(context.Background()) assert.NoError(t, err) assert.NotEmpty(t, output) for _, sms := range output { assert.NotEmpty(t, sms.Metrics) for _, ms := range sms.Metrics { sts := tt.startTimeFn(ms.Data) assert.NotEmpty(t, sts) for _, st := range sts { assert.True(t, st.After(processStartTime)) } } } }) } } open-telemetry-opentelemetry-go-contrib-e5abccb/config/000077500000000000000000000000001470323427300235165ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/config/config.go000066400000000000000000000076141470323427300253220ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package config // import "go.opentelemetry.io/contrib/config" import ( "context" "errors" "gopkg.in/yaml.v3" "go.opentelemetry.io/otel/log" nooplog "go.opentelemetry.io/otel/log/noop" "go.opentelemetry.io/otel/metric" noopmetric "go.opentelemetry.io/otel/metric/noop" "go.opentelemetry.io/otel/trace" nooptrace "go.opentelemetry.io/otel/trace/noop" ) const ( protocolProtobufHTTP = "http/protobuf" protocolProtobufGRPC = "grpc/protobuf" compressionGzip = "gzip" compressionNone = "none" ) type configOptions struct { ctx context.Context opentelemetryConfig OpenTelemetryConfiguration } type shutdownFunc func(context.Context) error func noopShutdown(context.Context) error { return nil } // SDK is a struct that contains all the providers // configured via the configuration model. type SDK struct { meterProvider metric.MeterProvider tracerProvider trace.TracerProvider loggerProvider log.LoggerProvider shutdown shutdownFunc } // TracerProvider returns a configured trace.TracerProvider. func (s *SDK) TracerProvider() trace.TracerProvider { return s.tracerProvider } // MeterProvider returns a configured metric.MeterProvider. func (s *SDK) MeterProvider() metric.MeterProvider { return s.meterProvider } // LoggerProvider returns a configured log.LoggerProvider. func (s *SDK) LoggerProvider() log.LoggerProvider { return s.loggerProvider } // Shutdown calls shutdown on all configured providers. func (s *SDK) Shutdown(ctx context.Context) error { return s.shutdown(ctx) } var noopSDK = SDK{ loggerProvider: nooplog.LoggerProvider{}, meterProvider: noopmetric.MeterProvider{}, tracerProvider: nooptrace.TracerProvider{}, shutdown: func(ctx context.Context) error { return nil }, } // NewSDK creates SDK providers based on the configuration model. func NewSDK(opts ...ConfigurationOption) (SDK, error) { o := configOptions{} for _, opt := range opts { o = opt.apply(o) } if o.opentelemetryConfig.Disabled != nil && *o.opentelemetryConfig.Disabled { return noopSDK, nil } r, err := newResource(o.opentelemetryConfig.Resource) if err != nil { return noopSDK, err } mp, mpShutdown, err := meterProvider(o, r) if err != nil { return noopSDK, err } tp, tpShutdown, err := tracerProvider(o, r) if err != nil { return noopSDK, err } lp, lpShutdown, err := loggerProvider(o, r) if err != nil { return noopSDK, err } return SDK{ meterProvider: mp, tracerProvider: tp, loggerProvider: lp, shutdown: func(ctx context.Context) error { return errors.Join(mpShutdown(ctx), tpShutdown(ctx), lpShutdown(ctx)) }, }, nil } // ConfigurationOption configures options for providers. type ConfigurationOption interface { apply(configOptions) configOptions } type configurationOptionFunc func(configOptions) configOptions func (fn configurationOptionFunc) apply(cfg configOptions) configOptions { return fn(cfg) } // WithContext sets the context.Context for the SDK. func WithContext(ctx context.Context) ConfigurationOption { return configurationOptionFunc(func(c configOptions) configOptions { c.ctx = ctx return c }) } // WithOpenTelemetryConfiguration sets the OpenTelemetryConfiguration used // to produce the SDK. func WithOpenTelemetryConfiguration(cfg OpenTelemetryConfiguration) ConfigurationOption { return configurationOptionFunc(func(c configOptions) configOptions { c.opentelemetryConfig = cfg return c }) } // ParseYAML parses a YAML configuration file into an OpenTelemetryConfiguration. func ParseYAML(file []byte) (*OpenTelemetryConfiguration, error) { var cfg OpenTelemetryConfiguration err := yaml.Unmarshal(file, &cfg) if err != nil { return nil, err } return &cfg, nil } func toStringMap(pairs []NameStringValuePair) map[string]string { output := make(map[string]string) for _, v := range pairs { output[v.Name] = *v.Value } return output } open-telemetry-opentelemetry-go-contrib-e5abccb/config/config_json.go000066400000000000000000000231721470323427300263500ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package config // import "go.opentelemetry.io/contrib/config" import ( "encoding/json" "fmt" "reflect" ) // MarshalJSON implements json.Marshaler. func (j *AttributeNameValueType) MarshalJSON() ([]byte, error) { return json.Marshal(j.Value) } var enumValuesAttributeNameValueType = []interface{}{ nil, "string", "bool", "int", "double", "string_array", "bool_array", "int_array", "double_array", } // UnmarshalJSON implements json.Unmarshaler. func (j *AttributeNameValueType) UnmarshalJSON(b []byte) error { var v struct { Value interface{} } if err := json.Unmarshal(b, &v.Value); err != nil { return err } var ok bool for _, expected := range enumValuesAttributeNameValueType { if reflect.DeepEqual(v.Value, expected) { ok = true break } } if !ok { return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValuesAttributeNameValueType, v.Value) } *j = AttributeNameValueType(v) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *BatchLogRecordProcessor) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } if _, ok := raw["exporter"]; raw != nil && !ok { return fmt.Errorf("field exporter in BatchLogRecordProcessor: required") } type Plain BatchLogRecordProcessor var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } *j = BatchLogRecordProcessor(plain) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *BatchSpanProcessor) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } if _, ok := raw["exporter"]; raw != nil && !ok { return fmt.Errorf("field exporter in BatchSpanProcessor: required") } type Plain BatchSpanProcessor var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } *j = BatchSpanProcessor(plain) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *GeneralInstrumentationPeerServiceMappingElem) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } if _, ok := raw["peer"]; raw != nil && !ok { return fmt.Errorf("field peer in GeneralInstrumentationPeerServiceMappingElem: required") } if _, ok := raw["service"]; raw != nil && !ok { return fmt.Errorf("field service in GeneralInstrumentationPeerServiceMappingElem: required") } type Plain GeneralInstrumentationPeerServiceMappingElem var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } *j = GeneralInstrumentationPeerServiceMappingElem(plain) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *NameStringValuePair) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } if _, ok := raw["name"]; raw != nil && !ok { return fmt.Errorf("field name in NameStringValuePair: required") } if _, ok := raw["value"]; raw != nil && !ok { return fmt.Errorf("field value in NameStringValuePair: required") } type Plain NameStringValuePair var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } *j = NameStringValuePair(plain) return nil } var enumValuesOTLPMetricDefaultHistogramAggregation = []interface{}{ "explicit_bucket_histogram", "base2_exponential_bucket_histogram", } // UnmarshalJSON implements json.Unmarshaler. func (j *OTLPMetricDefaultHistogramAggregation) UnmarshalJSON(b []byte) error { var v string if err := json.Unmarshal(b, &v); err != nil { return err } var ok bool for _, expected := range enumValuesOTLPMetricDefaultHistogramAggregation { if reflect.DeepEqual(v, expected) { ok = true break } } if !ok { return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValuesOTLPMetricDefaultHistogramAggregation, v) } *j = OTLPMetricDefaultHistogramAggregation(v) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *OTLPMetric) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } if _, ok := raw["endpoint"]; raw != nil && !ok { return fmt.Errorf("field endpoint in OTLPMetric: required") } if _, ok := raw["protocol"]; raw != nil && !ok { return fmt.Errorf("field protocol in OTLPMetric: required") } type Plain OTLPMetric var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } *j = OTLPMetric(plain) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *OTLP) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } if _, ok := raw["endpoint"]; raw != nil && !ok { return fmt.Errorf("field endpoint in OTLP: required") } if _, ok := raw["protocol"]; raw != nil && !ok { return fmt.Errorf("field protocol in OTLP: required") } type Plain OTLP var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } *j = OTLP(plain) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *OpenTelemetryConfiguration) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } if _, ok := raw["file_format"]; raw != nil && !ok { return fmt.Errorf("field file_format in OpenTelemetryConfiguration: required") } type Plain OpenTelemetryConfiguration var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } *j = OpenTelemetryConfiguration(plain) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *PeriodicMetricReader) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } if _, ok := raw["exporter"]; raw != nil && !ok { return fmt.Errorf("field exporter in PeriodicMetricReader: required") } type Plain PeriodicMetricReader var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } *j = PeriodicMetricReader(plain) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *PullMetricReader) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } if _, ok := raw["exporter"]; raw != nil && !ok { return fmt.Errorf("field exporter in PullMetricReader: required") } type Plain PullMetricReader var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } *j = PullMetricReader(plain) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *SimpleLogRecordProcessor) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } if _, ok := raw["exporter"]; raw != nil && !ok { return fmt.Errorf("field exporter in SimpleLogRecordProcessor: required") } type Plain SimpleLogRecordProcessor var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } *j = SimpleLogRecordProcessor(plain) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *SimpleSpanProcessor) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } if _, ok := raw["exporter"]; raw != nil && !ok { return fmt.Errorf("field exporter in SimpleSpanProcessor: required") } type Plain SimpleSpanProcessor var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } *j = SimpleSpanProcessor(plain) return nil } var enumValuesViewSelectorInstrumentType = []interface{}{ "counter", "histogram", "observable_counter", "observable_gauge", "observable_up_down_counter", "up_down_counter", } // UnmarshalJSON implements json.Unmarshaler. func (j *ViewSelectorInstrumentType) UnmarshalJSON(b []byte) error { var v string if err := json.Unmarshal(b, &v); err != nil { return err } var ok bool for _, expected := range enumValuesViewSelectorInstrumentType { if reflect.DeepEqual(v, expected) { ok = true break } } if !ok { return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValuesViewSelectorInstrumentType, v) } *j = ViewSelectorInstrumentType(v) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *Zipkin) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } if _, ok := raw["endpoint"]; raw != nil && !ok { return fmt.Errorf("field endpoint in Zipkin: required") } type Plain Zipkin var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } *j = Zipkin(plain) return nil } // UnmarshalJSON implements json.Unmarshaler. func (j *AttributeNameValue) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } if _, ok := raw["name"]; raw != nil && !ok { return fmt.Errorf("field name in AttributeNameValue: required") } if _, ok := raw["value"]; raw != nil && !ok { return fmt.Errorf("field value in AttributeNameValue: required") } type Plain AttributeNameValue var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } if plain.Type != nil && plain.Type.Value == "int" { val, ok := plain.Value.(float64) if ok { plain.Value = int(val) } } if plain.Type != nil && plain.Type.Value == "int_array" { m, ok := plain.Value.([]interface{}) if ok { var vals []interface{} for _, v := range m { val, ok := v.(float64) if ok { vals = append(vals, int(val)) } else { vals = append(vals, val) } } plain.Value = vals } } *j = AttributeNameValue(plain) return nil } open-telemetry-opentelemetry-go-contrib-e5abccb/config/config_test.go000066400000000000000000000336001470323427300263530ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package config import ( "context" "encoding/json" "errors" "os" "path/filepath" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" lognoop "go.opentelemetry.io/otel/log/noop" metricnoop "go.opentelemetry.io/otel/metric/noop" sdklog "go.opentelemetry.io/otel/sdk/log" sdkmetric "go.opentelemetry.io/otel/sdk/metric" sdktrace "go.opentelemetry.io/otel/sdk/trace" tracenoop "go.opentelemetry.io/otel/trace/noop" ) func TestNewSDK(t *testing.T) { tests := []struct { name string cfg []ConfigurationOption wantTracerProvider any wantMeterProvider any wantLoggerProvider any wantErr error wantShutdownErr error }{ { name: "no-configuration", wantTracerProvider: tracenoop.NewTracerProvider(), wantMeterProvider: metricnoop.NewMeterProvider(), wantLoggerProvider: lognoop.NewLoggerProvider(), }, { name: "with-configuration", cfg: []ConfigurationOption{ WithContext(context.Background()), WithOpenTelemetryConfiguration(OpenTelemetryConfiguration{ TracerProvider: &TracerProvider{}, MeterProvider: &MeterProvider{}, LoggerProvider: &LoggerProvider{}, }), }, wantTracerProvider: &sdktrace.TracerProvider{}, wantMeterProvider: &sdkmetric.MeterProvider{}, wantLoggerProvider: &sdklog.LoggerProvider{}, }, { name: "with-sdk-disabled", cfg: []ConfigurationOption{ WithContext(context.Background()), WithOpenTelemetryConfiguration(OpenTelemetryConfiguration{ Disabled: ptr(true), TracerProvider: &TracerProvider{}, MeterProvider: &MeterProvider{}, LoggerProvider: &LoggerProvider{}, }), }, wantTracerProvider: tracenoop.NewTracerProvider(), wantMeterProvider: metricnoop.NewMeterProvider(), wantLoggerProvider: lognoop.NewLoggerProvider(), }, } for _, tt := range tests { sdk, err := NewSDK(tt.cfg...) require.Equal(t, tt.wantErr, err) assert.IsType(t, tt.wantTracerProvider, sdk.TracerProvider()) assert.IsType(t, tt.wantMeterProvider, sdk.MeterProvider()) assert.IsType(t, tt.wantLoggerProvider, sdk.LoggerProvider()) require.Equal(t, tt.wantShutdownErr, sdk.Shutdown(context.Background())) } } var v03OpenTelemetryConfig = OpenTelemetryConfiguration{ Disabled: ptr(false), FileFormat: ptr("0.3"), AttributeLimits: &AttributeLimits{ AttributeCountLimit: ptr(128), AttributeValueLengthLimit: ptr(4096), }, Instrumentation: &Instrumentation{ Cpp: LanguageSpecificInstrumentation{ "example": map[string]interface{}{ "property": "value", }, }, Dotnet: LanguageSpecificInstrumentation{ "example": map[string]interface{}{ "property": "value", }, }, Erlang: LanguageSpecificInstrumentation{ "example": map[string]interface{}{ "property": "value", }, }, General: &GeneralInstrumentation{ Http: &GeneralInstrumentationHttp{ Client: &GeneralInstrumentationHttpClient{ RequestCapturedHeaders: []string{"Content-Type", "Accept"}, ResponseCapturedHeaders: []string{"Content-Type", "Content-Encoding"}, }, Server: &GeneralInstrumentationHttpServer{ RequestCapturedHeaders: []string{"Content-Type", "Accept"}, ResponseCapturedHeaders: []string{"Content-Type", "Content-Encoding"}, }, }, Peer: &GeneralInstrumentationPeer{ ServiceMapping: []GeneralInstrumentationPeerServiceMappingElem{ {Peer: "1.2.3.4", Service: "FooService"}, {Peer: "2.3.4.5", Service: "BarService"}, }, }, }, Go: LanguageSpecificInstrumentation{ "example": map[string]interface{}{ "property": "value", }, }, Java: LanguageSpecificInstrumentation{ "example": map[string]interface{}{ "property": "value", }, }, Js: LanguageSpecificInstrumentation{ "example": map[string]interface{}{ "property": "value", }, }, Php: LanguageSpecificInstrumentation{ "example": map[string]interface{}{ "property": "value", }, }, Python: LanguageSpecificInstrumentation{ "example": map[string]interface{}{ "property": "value", }, }, Ruby: LanguageSpecificInstrumentation{ "example": map[string]interface{}{ "property": "value", }, }, Rust: LanguageSpecificInstrumentation{ "example": map[string]interface{}{ "property": "value", }, }, Swift: LanguageSpecificInstrumentation{ "example": map[string]interface{}{ "property": "value", }, }, }, LoggerProvider: &LoggerProvider{ Limits: &LogRecordLimits{ AttributeCountLimit: ptr(128), AttributeValueLengthLimit: ptr(4096), }, Processors: []LogRecordProcessor{ { Batch: &BatchLogRecordProcessor{ ExportTimeout: ptr(30000), Exporter: LogRecordExporter{ OTLP: &OTLP{ Certificate: ptr("/app/cert.pem"), ClientCertificate: ptr("/app/cert.pem"), ClientKey: ptr("/app/cert.pem"), Compression: ptr("gzip"), Endpoint: ptr("http://localhost:4318/v1/logs"), Headers: []NameStringValuePair{ {Name: "api-key", Value: ptr("1234")}, }, HeadersList: ptr("api-key=1234"), Insecure: ptr(false), Protocol: ptr("http/protobuf"), Timeout: ptr(10000), }, }, MaxExportBatchSize: ptr(512), MaxQueueSize: ptr(2048), ScheduleDelay: ptr(5000), }, }, { Simple: &SimpleLogRecordProcessor{ Exporter: LogRecordExporter{ Console: Console{}, }, }, }, }, }, MeterProvider: &MeterProvider{ Readers: []MetricReader{ { Producers: []MetricProducer{ {Opencensus: MetricProducerOpencensus{}}, }, Pull: &PullMetricReader{ Exporter: PullMetricExporter{ Prometheus: &Prometheus{ Host: ptr("localhost"), Port: ptr(9464), WithResourceConstantLabels: &IncludeExclude{ Excluded: []string{"service.attr1"}, Included: []string{"service*"}, }, WithoutScopeInfo: ptr(false), WithoutTypeSuffix: ptr(false), WithoutUnits: ptr(false), }, }, }, }, { Producers: []MetricProducer{ {}, }, Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Certificate: ptr("/app/cert.pem"), ClientCertificate: ptr("/app/cert.pem"), ClientKey: ptr("/app/cert.pem"), Compression: ptr("gzip"), DefaultHistogramAggregation: ptr(OTLPMetricDefaultHistogramAggregationBase2ExponentialBucketHistogram), Endpoint: ptr("http://localhost:4318/v1/metrics"), Headers: []NameStringValuePair{ {Name: "api-key", Value: ptr("1234")}, }, HeadersList: ptr("api-key=1234"), Insecure: ptr(false), Protocol: ptr("http/protobuf"), TemporalityPreference: ptr("delta"), Timeout: ptr(10000), }, }, Interval: ptr(5000), Timeout: ptr(30000), }, }, { Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ Console: Console{}, }, }, }, }, Views: []View{ { Selector: &ViewSelector{ InstrumentName: ptr("my-instrument"), InstrumentType: ptr(ViewSelectorInstrumentTypeHistogram), MeterName: ptr("my-meter"), MeterSchemaUrl: ptr("https://opentelemetry.io/schemas/1.16.0"), MeterVersion: ptr("1.0.0"), Unit: ptr("ms"), }, Stream: &ViewStream{ Aggregation: &ViewStreamAggregation{ ExplicitBucketHistogram: &ViewStreamAggregationExplicitBucketHistogram{ Boundaries: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, RecordMinMax: ptr(true), }, }, AttributeKeys: &IncludeExclude{ Included: []string{"key1", "key2"}, Excluded: []string{"key3"}, }, Description: ptr("new_description"), Name: ptr("new_instrument_name"), }, }, }, }, Propagator: &Propagator{ Composite: []*string{ptr("tracecontext"), ptr("baggage"), ptr("b3"), ptr("b3multi"), ptr("jaeger"), ptr("xray"), ptr("ottrace")}, }, Resource: &Resource{ Attributes: []AttributeNameValue{ {Name: "service.name", Value: "unknown_service"}, {Name: "string_key", Type: &AttributeNameValueType{Value: "string"}, Value: "value"}, {Name: "bool_key", Type: &AttributeNameValueType{Value: "bool"}, Value: true}, {Name: "int_key", Type: &AttributeNameValueType{Value: "int"}, Value: 1}, {Name: "double_key", Type: &AttributeNameValueType{Value: "double"}, Value: 1.1}, {Name: "string_array_key", Type: &AttributeNameValueType{Value: "string_array"}, Value: []interface{}{"value1", "value2"}}, {Name: "bool_array_key", Type: &AttributeNameValueType{Value: "bool_array"}, Value: []interface{}{true, false}}, {Name: "int_array_key", Type: &AttributeNameValueType{Value: "int_array"}, Value: []interface{}{1, 2}}, {Name: "double_array_key", Type: &AttributeNameValueType{Value: "double_array"}, Value: []interface{}{1.1, 2.2}}, }, AttributesList: ptr("service.namespace=my-namespace,service.version=1.0.0"), Detectors: &Detectors{ Attributes: &DetectorsAttributes{ Excluded: []string{"process.command_args"}, Included: []string{"process.*"}, }, }, SchemaUrl: ptr("https://opentelemetry.io/schemas/1.16.0"), }, TracerProvider: &TracerProvider{ Limits: &SpanLimits{ AttributeCountLimit: ptr(128), AttributeValueLengthLimit: ptr(4096), EventCountLimit: ptr(128), EventAttributeCountLimit: ptr(128), LinkCountLimit: ptr(128), LinkAttributeCountLimit: ptr(128), }, Processors: []SpanProcessor{ { Batch: &BatchSpanProcessor{ ExportTimeout: ptr(30000), Exporter: SpanExporter{ OTLP: &OTLP{ Certificate: ptr("/app/cert.pem"), ClientCertificate: ptr("/app/cert.pem"), ClientKey: ptr("/app/cert.pem"), Compression: ptr("gzip"), Endpoint: ptr("http://localhost:4318/v1/traces"), Headers: []NameStringValuePair{ {Name: "api-key", Value: ptr("1234")}, }, HeadersList: ptr("api-key=1234"), Insecure: ptr(false), Protocol: ptr("http/protobuf"), Timeout: ptr(10000), }, }, MaxExportBatchSize: ptr(512), MaxQueueSize: ptr(2048), ScheduleDelay: ptr(5000), }, }, { Batch: &BatchSpanProcessor{ Exporter: SpanExporter{ Zipkin: &Zipkin{ Endpoint: ptr("http://localhost:9411/api/v2/spans"), Timeout: ptr(10000), }, }, }, }, { Simple: &SimpleSpanProcessor{ Exporter: SpanExporter{ Console: Console{}, }, }, }, }, Sampler: &Sampler{ ParentBased: &SamplerParentBased{ LocalParentNotSampled: &Sampler{ AlwaysOff: SamplerAlwaysOff{}, }, LocalParentSampled: &Sampler{ AlwaysOn: SamplerAlwaysOn{}, }, RemoteParentNotSampled: &Sampler{ AlwaysOff: SamplerAlwaysOff{}, }, RemoteParentSampled: &Sampler{ AlwaysOn: SamplerAlwaysOn{}, }, Root: &Sampler{ TraceIDRatioBased: &SamplerTraceIDRatioBased{ Ratio: ptr(0.0001), }, }, }, }, }, } func TestParseYAML(t *testing.T) { tests := []struct { name string input string wantErr error wantType interface{} }{ { name: "valid YAML config", input: `valid_empty.yaml`, wantErr: nil, wantType: &OpenTelemetryConfiguration{ Disabled: ptr(false), FileFormat: ptr("0.1"), }, }, { name: "invalid config", input: "invalid_bool.yaml", wantErr: errors.New(`yaml: unmarshal errors: line 2: cannot unmarshal !!str ` + "`notabool`" + ` into bool`), }, { name: "valid v0.2 config", input: "v0.2.yaml", wantErr: errors.New(`yaml: unmarshal errors: line 81: cannot unmarshal !!map into []config.NameStringValuePair line 185: cannot unmarshal !!map into []config.NameStringValuePair line 244: cannot unmarshal !!seq into config.IncludeExclude line 305: cannot unmarshal !!map into []config.NameStringValuePair line 408: cannot unmarshal !!map into []config.AttributeNameValue`), }, { name: "valid v0.3 config", input: "v0.3.yaml", wantType: &v03OpenTelemetryConfig, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { b, err := os.ReadFile(filepath.Join("testdata", tt.input)) require.NoError(t, err) got, err := ParseYAML(b) if tt.wantErr != nil { require.Equal(t, tt.wantErr.Error(), err.Error()) } else { require.NoError(t, err) assert.Equal(t, tt.wantType, got) } }) } } func TestSerializeJSON(t *testing.T) { tests := []struct { name string input string wantErr error wantType interface{} }{ { name: "valid JSON config", input: `valid_empty.json`, wantErr: nil, wantType: OpenTelemetryConfiguration{ Disabled: ptr(false), FileFormat: ptr("0.1"), }, }, { name: "invalid config", input: "invalid_bool.json", wantErr: errors.New(`json: cannot unmarshal string into Go struct field Plain.disabled of type bool`), }, { name: "valid v0.2 config", input: "v0.2.json", wantErr: errors.New(`json: cannot unmarshal object into Go struct field LogRecordProcessor.logger_provider.processors.batch of type []config.NameStringValuePair`), }, { name: "valid v0.3 config", input: "v0.3.json", wantType: v03OpenTelemetryConfig, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { b, err := os.ReadFile(filepath.Join("testdata", tt.input)) require.NoError(t, err) var got OpenTelemetryConfiguration err = json.Unmarshal(b, &got) if tt.wantErr != nil { require.Equal(t, tt.wantErr.Error(), err.Error()) } else { require.NoError(t, err) assert.Equal(t, tt.wantType, got) } }) } } func ptr[T any](v T) *T { return &v } open-telemetry-opentelemetry-go-contrib-e5abccb/config/config_yaml.go000066400000000000000000000017251470323427300263410ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package config // import "go.opentelemetry.io/contrib/config" import ( "fmt" "reflect" ) // UnmarshalYAML implements yaml.Unmarshaler. func (j *AttributeNameValueType) UnmarshalYAML(unmarshal func(interface{}) error) error { var v struct { Value interface{} } if err := unmarshal(&v.Value); err != nil { return err } var ok bool for _, expected := range enumValuesAttributeNameValueType { if reflect.DeepEqual(v.Value, expected) { ok = true break } } if !ok { return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValuesAttributeNameValueType, v.Value) } *j = AttributeNameValueType(v) return nil } // UnmarshalYAML implements yaml.Unmarshaler. func (j *LanguageSpecificInstrumentation) UnmarshalYAML(unmarshal func(interface{}) error) error { var raw map[string]interface{} if err := unmarshal(&raw); err != nil { return err } *j = raw return nil } open-telemetry-opentelemetry-go-contrib-e5abccb/config/doc.go000066400000000000000000000004371470323427300246160ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package config can be used to parse a configuration file // that follows the JSON Schema defined by the OpenTelemetry // Configuration schema. package config // import "go.opentelemetry.io/contrib/config" open-telemetry-opentelemetry-go-contrib-e5abccb/config/generated_config.go000066400000000000000000001034471470323427300273410ustar00rootroot00000000000000// Code generated by github.com/atombender/go-jsonschema, DO NOT EDIT. package config type AttributeLimits struct { // AttributeCountLimit corresponds to the JSON schema field // "attribute_count_limit". AttributeCountLimit *int `json:"attribute_count_limit,omitempty" yaml:"attribute_count_limit,omitempty" mapstructure:"attribute_count_limit,omitempty"` // AttributeValueLengthLimit corresponds to the JSON schema field // "attribute_value_length_limit". AttributeValueLengthLimit *int `json:"attribute_value_length_limit,omitempty" yaml:"attribute_value_length_limit,omitempty" mapstructure:"attribute_value_length_limit,omitempty"` AdditionalProperties interface{} } type AttributeNameValue struct { // Name corresponds to the JSON schema field "name". Name string `json:"name" yaml:"name" mapstructure:"name"` // Type corresponds to the JSON schema field "type". Type *AttributeNameValueType `json:"type,omitempty" yaml:"type,omitempty" mapstructure:"type,omitempty"` // Value corresponds to the JSON schema field "value". Value interface{} `json:"value" yaml:"value" mapstructure:"value"` } type AttributeNameValueType struct { Value interface{} } type BatchLogRecordProcessor struct { // ExportTimeout corresponds to the JSON schema field "export_timeout". ExportTimeout *int `json:"export_timeout,omitempty" yaml:"export_timeout,omitempty" mapstructure:"export_timeout,omitempty"` // Exporter corresponds to the JSON schema field "exporter". Exporter LogRecordExporter `json:"exporter" yaml:"exporter" mapstructure:"exporter"` // MaxExportBatchSize corresponds to the JSON schema field // "max_export_batch_size". MaxExportBatchSize *int `json:"max_export_batch_size,omitempty" yaml:"max_export_batch_size,omitempty" mapstructure:"max_export_batch_size,omitempty"` // MaxQueueSize corresponds to the JSON schema field "max_queue_size". MaxQueueSize *int `json:"max_queue_size,omitempty" yaml:"max_queue_size,omitempty" mapstructure:"max_queue_size,omitempty"` // ScheduleDelay corresponds to the JSON schema field "schedule_delay". ScheduleDelay *int `json:"schedule_delay,omitempty" yaml:"schedule_delay,omitempty" mapstructure:"schedule_delay,omitempty"` } type BatchSpanProcessor struct { // ExportTimeout corresponds to the JSON schema field "export_timeout". ExportTimeout *int `json:"export_timeout,omitempty" yaml:"export_timeout,omitempty" mapstructure:"export_timeout,omitempty"` // Exporter corresponds to the JSON schema field "exporter". Exporter SpanExporter `json:"exporter" yaml:"exporter" mapstructure:"exporter"` // MaxExportBatchSize corresponds to the JSON schema field // "max_export_batch_size". MaxExportBatchSize *int `json:"max_export_batch_size,omitempty" yaml:"max_export_batch_size,omitempty" mapstructure:"max_export_batch_size,omitempty"` // MaxQueueSize corresponds to the JSON schema field "max_queue_size". MaxQueueSize *int `json:"max_queue_size,omitempty" yaml:"max_queue_size,omitempty" mapstructure:"max_queue_size,omitempty"` // ScheduleDelay corresponds to the JSON schema field "schedule_delay". ScheduleDelay *int `json:"schedule_delay,omitempty" yaml:"schedule_delay,omitempty" mapstructure:"schedule_delay,omitempty"` } type Common map[string]interface{} type Console map[string]interface{} type Detectors struct { // Attributes corresponds to the JSON schema field "attributes". Attributes *DetectorsAttributes `json:"attributes,omitempty" yaml:"attributes,omitempty" mapstructure:"attributes,omitempty"` } type DetectorsAttributes struct { // Excluded corresponds to the JSON schema field "excluded". Excluded []string `json:"excluded,omitempty" yaml:"excluded,omitempty" mapstructure:"excluded,omitempty"` // Included corresponds to the JSON schema field "included". Included []string `json:"included,omitempty" yaml:"included,omitempty" mapstructure:"included,omitempty"` } type GeneralInstrumentation struct { // Http corresponds to the JSON schema field "http". Http *GeneralInstrumentationHttp `json:"http,omitempty" yaml:"http,omitempty" mapstructure:"http,omitempty"` // Peer corresponds to the JSON schema field "peer". Peer *GeneralInstrumentationPeer `json:"peer,omitempty" yaml:"peer,omitempty" mapstructure:"peer,omitempty"` } type GeneralInstrumentationHttp struct { // Client corresponds to the JSON schema field "client". Client *GeneralInstrumentationHttpClient `json:"client,omitempty" yaml:"client,omitempty" mapstructure:"client,omitempty"` // Server corresponds to the JSON schema field "server". Server *GeneralInstrumentationHttpServer `json:"server,omitempty" yaml:"server,omitempty" mapstructure:"server,omitempty"` } type GeneralInstrumentationHttpClient struct { // RequestCapturedHeaders corresponds to the JSON schema field // "request_captured_headers". RequestCapturedHeaders []string `json:"request_captured_headers,omitempty" yaml:"request_captured_headers,omitempty" mapstructure:"request_captured_headers,omitempty"` // ResponseCapturedHeaders corresponds to the JSON schema field // "response_captured_headers". ResponseCapturedHeaders []string `json:"response_captured_headers,omitempty" yaml:"response_captured_headers,omitempty" mapstructure:"response_captured_headers,omitempty"` } type GeneralInstrumentationHttpServer struct { // RequestCapturedHeaders corresponds to the JSON schema field // "request_captured_headers". RequestCapturedHeaders []string `json:"request_captured_headers,omitempty" yaml:"request_captured_headers,omitempty" mapstructure:"request_captured_headers,omitempty"` // ResponseCapturedHeaders corresponds to the JSON schema field // "response_captured_headers". ResponseCapturedHeaders []string `json:"response_captured_headers,omitempty" yaml:"response_captured_headers,omitempty" mapstructure:"response_captured_headers,omitempty"` } type GeneralInstrumentationPeer struct { // ServiceMapping corresponds to the JSON schema field "service_mapping". ServiceMapping []GeneralInstrumentationPeerServiceMappingElem `json:"service_mapping,omitempty" yaml:"service_mapping,omitempty" mapstructure:"service_mapping,omitempty"` } type GeneralInstrumentationPeerServiceMappingElem struct { // Peer corresponds to the JSON schema field "peer". Peer string `json:"peer" yaml:"peer" mapstructure:"peer"` // Service corresponds to the JSON schema field "service". Service string `json:"service" yaml:"service" mapstructure:"service"` } type IncludeExclude struct { // Excluded corresponds to the JSON schema field "excluded". Excluded []string `json:"excluded,omitempty" yaml:"excluded,omitempty" mapstructure:"excluded,omitempty"` // Included corresponds to the JSON schema field "included". Included []string `json:"included,omitempty" yaml:"included,omitempty" mapstructure:"included,omitempty"` } type Instrumentation struct { // Cpp corresponds to the JSON schema field "cpp". Cpp LanguageSpecificInstrumentation `json:"cpp,omitempty" yaml:"cpp,omitempty" mapstructure:"cpp,omitempty"` // Dotnet corresponds to the JSON schema field "dotnet". Dotnet LanguageSpecificInstrumentation `json:"dotnet,omitempty" yaml:"dotnet,omitempty" mapstructure:"dotnet,omitempty"` // Erlang corresponds to the JSON schema field "erlang". Erlang LanguageSpecificInstrumentation `json:"erlang,omitempty" yaml:"erlang,omitempty" mapstructure:"erlang,omitempty"` // General corresponds to the JSON schema field "general". General *GeneralInstrumentation `json:"general,omitempty" yaml:"general,omitempty" mapstructure:"general,omitempty"` // Go corresponds to the JSON schema field "go". Go LanguageSpecificInstrumentation `json:"go,omitempty" yaml:"go,omitempty" mapstructure:"go,omitempty"` // Java corresponds to the JSON schema field "java". Java LanguageSpecificInstrumentation `json:"java,omitempty" yaml:"java,omitempty" mapstructure:"java,omitempty"` // Js corresponds to the JSON schema field "js". Js LanguageSpecificInstrumentation `json:"js,omitempty" yaml:"js,omitempty" mapstructure:"js,omitempty"` // Php corresponds to the JSON schema field "php". Php LanguageSpecificInstrumentation `json:"php,omitempty" yaml:"php,omitempty" mapstructure:"php,omitempty"` // Python corresponds to the JSON schema field "python". Python LanguageSpecificInstrumentation `json:"python,omitempty" yaml:"python,omitempty" mapstructure:"python,omitempty"` // Ruby corresponds to the JSON schema field "ruby". Ruby LanguageSpecificInstrumentation `json:"ruby,omitempty" yaml:"ruby,omitempty" mapstructure:"ruby,omitempty"` // Rust corresponds to the JSON schema field "rust". Rust LanguageSpecificInstrumentation `json:"rust,omitempty" yaml:"rust,omitempty" mapstructure:"rust,omitempty"` // Swift corresponds to the JSON schema field "swift". Swift LanguageSpecificInstrumentation `json:"swift,omitempty" yaml:"swift,omitempty" mapstructure:"swift,omitempty"` } type LanguageSpecificInstrumentation map[string]interface{} type LogRecordExporter struct { // Console corresponds to the JSON schema field "console". Console Console `json:"console,omitempty" yaml:"console,omitempty" mapstructure:"console,omitempty"` // OTLP corresponds to the JSON schema field "otlp". OTLP *OTLP `json:"otlp,omitempty" yaml:"otlp,omitempty" mapstructure:"otlp,omitempty"` AdditionalProperties interface{} } type LogRecordLimits struct { // AttributeCountLimit corresponds to the JSON schema field // "attribute_count_limit". AttributeCountLimit *int `json:"attribute_count_limit,omitempty" yaml:"attribute_count_limit,omitempty" mapstructure:"attribute_count_limit,omitempty"` // AttributeValueLengthLimit corresponds to the JSON schema field // "attribute_value_length_limit". AttributeValueLengthLimit *int `json:"attribute_value_length_limit,omitempty" yaml:"attribute_value_length_limit,omitempty" mapstructure:"attribute_value_length_limit,omitempty"` } type LogRecordProcessor struct { // Batch corresponds to the JSON schema field "batch". Batch *BatchLogRecordProcessor `json:"batch,omitempty" yaml:"batch,omitempty" mapstructure:"batch,omitempty"` // Simple corresponds to the JSON schema field "simple". Simple *SimpleLogRecordProcessor `json:"simple,omitempty" yaml:"simple,omitempty" mapstructure:"simple,omitempty"` AdditionalProperties interface{} } type LoggerProvider struct { // Limits corresponds to the JSON schema field "limits". Limits *LogRecordLimits `json:"limits,omitempty" yaml:"limits,omitempty" mapstructure:"limits,omitempty"` // Processors corresponds to the JSON schema field "processors". Processors []LogRecordProcessor `json:"processors,omitempty" yaml:"processors,omitempty" mapstructure:"processors,omitempty"` } type MeterProvider struct { // Readers corresponds to the JSON schema field "readers". Readers []MetricReader `json:"readers,omitempty" yaml:"readers,omitempty" mapstructure:"readers,omitempty"` // Views corresponds to the JSON schema field "views". Views []View `json:"views,omitempty" yaml:"views,omitempty" mapstructure:"views,omitempty"` } type MetricProducer struct { // Opencensus corresponds to the JSON schema field "opencensus". Opencensus MetricProducerOpencensus `json:"opencensus,omitempty" yaml:"opencensus,omitempty" mapstructure:"opencensus,omitempty"` AdditionalProperties interface{} } type MetricProducerOpencensus map[string]interface{} type MetricReader struct { // Periodic corresponds to the JSON schema field "periodic". Periodic *PeriodicMetricReader `json:"periodic,omitempty" yaml:"periodic,omitempty" mapstructure:"periodic,omitempty"` // Producers corresponds to the JSON schema field "producers". Producers []MetricProducer `json:"producers,omitempty" yaml:"producers,omitempty" mapstructure:"producers,omitempty"` // Pull corresponds to the JSON schema field "pull". Pull *PullMetricReader `json:"pull,omitempty" yaml:"pull,omitempty" mapstructure:"pull,omitempty"` } type NameStringValuePair struct { // Name corresponds to the JSON schema field "name". Name string `json:"name" yaml:"name" mapstructure:"name"` // Value corresponds to the JSON schema field "value". Value *string `json:"value" yaml:"value" mapstructure:"value"` } type OTLP struct { // Certificate corresponds to the JSON schema field "certificate". Certificate *string `json:"certificate,omitempty" yaml:"certificate,omitempty" mapstructure:"certificate,omitempty"` // ClientCertificate corresponds to the JSON schema field "client_certificate". ClientCertificate *string `json:"client_certificate,omitempty" yaml:"client_certificate,omitempty" mapstructure:"client_certificate,omitempty"` // ClientKey corresponds to the JSON schema field "client_key". ClientKey *string `json:"client_key,omitempty" yaml:"client_key,omitempty" mapstructure:"client_key,omitempty"` // Compression corresponds to the JSON schema field "compression". Compression *string `json:"compression,omitempty" yaml:"compression,omitempty" mapstructure:"compression,omitempty"` // Endpoint corresponds to the JSON schema field "endpoint". Endpoint *string `json:"endpoint" yaml:"endpoint" mapstructure:"endpoint"` // Headers corresponds to the JSON schema field "headers". Headers []NameStringValuePair `json:"headers,omitempty" yaml:"headers,omitempty" mapstructure:"headers,omitempty"` // HeadersList corresponds to the JSON schema field "headers_list". HeadersList *string `json:"headers_list,omitempty" yaml:"headers_list,omitempty" mapstructure:"headers_list,omitempty"` // Insecure corresponds to the JSON schema field "insecure". Insecure *bool `json:"insecure,omitempty" yaml:"insecure,omitempty" mapstructure:"insecure,omitempty"` // Protocol corresponds to the JSON schema field "protocol". Protocol *string `json:"protocol" yaml:"protocol" mapstructure:"protocol"` // Timeout corresponds to the JSON schema field "timeout". Timeout *int `json:"timeout,omitempty" yaml:"timeout,omitempty" mapstructure:"timeout,omitempty"` } type OTLPMetric struct { // Certificate corresponds to the JSON schema field "certificate". Certificate *string `json:"certificate,omitempty" yaml:"certificate,omitempty" mapstructure:"certificate,omitempty"` // ClientCertificate corresponds to the JSON schema field "client_certificate". ClientCertificate *string `json:"client_certificate,omitempty" yaml:"client_certificate,omitempty" mapstructure:"client_certificate,omitempty"` // ClientKey corresponds to the JSON schema field "client_key". ClientKey *string `json:"client_key,omitempty" yaml:"client_key,omitempty" mapstructure:"client_key,omitempty"` // Compression corresponds to the JSON schema field "compression". Compression *string `json:"compression,omitempty" yaml:"compression,omitempty" mapstructure:"compression,omitempty"` // DefaultHistogramAggregation corresponds to the JSON schema field // "default_histogram_aggregation". DefaultHistogramAggregation *OTLPMetricDefaultHistogramAggregation `json:"default_histogram_aggregation,omitempty" yaml:"default_histogram_aggregation,omitempty" mapstructure:"default_histogram_aggregation,omitempty"` // Endpoint corresponds to the JSON schema field "endpoint". Endpoint *string `json:"endpoint" yaml:"endpoint" mapstructure:"endpoint"` // Headers corresponds to the JSON schema field "headers". Headers []NameStringValuePair `json:"headers,omitempty" yaml:"headers,omitempty" mapstructure:"headers,omitempty"` // HeadersList corresponds to the JSON schema field "headers_list". HeadersList *string `json:"headers_list,omitempty" yaml:"headers_list,omitempty" mapstructure:"headers_list,omitempty"` // Insecure corresponds to the JSON schema field "insecure". Insecure *bool `json:"insecure,omitempty" yaml:"insecure,omitempty" mapstructure:"insecure,omitempty"` // Protocol corresponds to the JSON schema field "protocol". Protocol *string `json:"protocol" yaml:"protocol" mapstructure:"protocol"` // TemporalityPreference corresponds to the JSON schema field // "temporality_preference". TemporalityPreference *string `json:"temporality_preference,omitempty" yaml:"temporality_preference,omitempty" mapstructure:"temporality_preference,omitempty"` // Timeout corresponds to the JSON schema field "timeout". Timeout *int `json:"timeout,omitempty" yaml:"timeout,omitempty" mapstructure:"timeout,omitempty"` } type OTLPMetricDefaultHistogramAggregation string const OTLPMetricDefaultHistogramAggregationBase2ExponentialBucketHistogram OTLPMetricDefaultHistogramAggregation = "base2_exponential_bucket_histogram" const OTLPMetricDefaultHistogramAggregationExplicitBucketHistogram OTLPMetricDefaultHistogramAggregation = "explicit_bucket_histogram" type OpenTelemetryConfiguration struct { // AttributeLimits corresponds to the JSON schema field "attribute_limits". AttributeLimits *AttributeLimits `json:"attribute_limits,omitempty" yaml:"attribute_limits,omitempty" mapstructure:"attribute_limits,omitempty"` // Disabled corresponds to the JSON schema field "disabled". Disabled *bool `json:"disabled,omitempty" yaml:"disabled,omitempty" mapstructure:"disabled,omitempty"` // FileFormat corresponds to the JSON schema field "file_format". FileFormat *string `json:"file_format" yaml:"file_format" mapstructure:"file_format"` // Instrumentation corresponds to the JSON schema field "instrumentation". Instrumentation *Instrumentation `json:"instrumentation,omitempty" yaml:"instrumentation,omitempty" mapstructure:"instrumentation,omitempty"` // LoggerProvider corresponds to the JSON schema field "logger_provider". LoggerProvider *LoggerProvider `json:"logger_provider,omitempty" yaml:"logger_provider,omitempty" mapstructure:"logger_provider,omitempty"` // MeterProvider corresponds to the JSON schema field "meter_provider". MeterProvider *MeterProvider `json:"meter_provider,omitempty" yaml:"meter_provider,omitempty" mapstructure:"meter_provider,omitempty"` // Propagator corresponds to the JSON schema field "propagator". Propagator *Propagator `json:"propagator,omitempty" yaml:"propagator,omitempty" mapstructure:"propagator,omitempty"` // Resource corresponds to the JSON schema field "resource". Resource *Resource `json:"resource,omitempty" yaml:"resource,omitempty" mapstructure:"resource,omitempty"` // TracerProvider corresponds to the JSON schema field "tracer_provider". TracerProvider *TracerProvider `json:"tracer_provider,omitempty" yaml:"tracer_provider,omitempty" mapstructure:"tracer_provider,omitempty"` AdditionalProperties interface{} } type PeriodicMetricReader struct { // Exporter corresponds to the JSON schema field "exporter". Exporter PushMetricExporter `json:"exporter" yaml:"exporter" mapstructure:"exporter"` // Interval corresponds to the JSON schema field "interval". Interval *int `json:"interval,omitempty" yaml:"interval,omitempty" mapstructure:"interval,omitempty"` // Timeout corresponds to the JSON schema field "timeout". Timeout *int `json:"timeout,omitempty" yaml:"timeout,omitempty" mapstructure:"timeout,omitempty"` } type Prometheus struct { // Host corresponds to the JSON schema field "host". Host *string `json:"host,omitempty" yaml:"host,omitempty" mapstructure:"host,omitempty"` // Port corresponds to the JSON schema field "port". Port *int `json:"port,omitempty" yaml:"port,omitempty" mapstructure:"port,omitempty"` // WithResourceConstantLabels corresponds to the JSON schema field // "with_resource_constant_labels". WithResourceConstantLabels *IncludeExclude `json:"with_resource_constant_labels,omitempty" yaml:"with_resource_constant_labels,omitempty" mapstructure:"with_resource_constant_labels,omitempty"` // WithoutScopeInfo corresponds to the JSON schema field "without_scope_info". WithoutScopeInfo *bool `json:"without_scope_info,omitempty" yaml:"without_scope_info,omitempty" mapstructure:"without_scope_info,omitempty"` // WithoutTypeSuffix corresponds to the JSON schema field "without_type_suffix". WithoutTypeSuffix *bool `json:"without_type_suffix,omitempty" yaml:"without_type_suffix,omitempty" mapstructure:"without_type_suffix,omitempty"` // WithoutUnits corresponds to the JSON schema field "without_units". WithoutUnits *bool `json:"without_units,omitempty" yaml:"without_units,omitempty" mapstructure:"without_units,omitempty"` } type Propagator struct { // Composite corresponds to the JSON schema field "composite". Composite []*string `json:"composite,omitempty" yaml:"composite,omitempty" mapstructure:"composite,omitempty"` AdditionalProperties interface{} } type PullMetricExporter struct { // Prometheus corresponds to the JSON schema field "prometheus". Prometheus *Prometheus `json:"prometheus,omitempty" yaml:"prometheus,omitempty" mapstructure:"prometheus,omitempty"` AdditionalProperties interface{} } type PullMetricReader struct { // Exporter corresponds to the JSON schema field "exporter". Exporter PullMetricExporter `json:"exporter" yaml:"exporter" mapstructure:"exporter"` } type PushMetricExporter struct { // Console corresponds to the JSON schema field "console". Console Console `json:"console,omitempty" yaml:"console,omitempty" mapstructure:"console,omitempty"` // OTLP corresponds to the JSON schema field "otlp". OTLP *OTLPMetric `json:"otlp,omitempty" yaml:"otlp,omitempty" mapstructure:"otlp,omitempty"` AdditionalProperties interface{} } type Resource struct { // Attributes corresponds to the JSON schema field "attributes". Attributes []AttributeNameValue `json:"attributes,omitempty" yaml:"attributes,omitempty" mapstructure:"attributes,omitempty"` // AttributesList corresponds to the JSON schema field "attributes_list". AttributesList *string `json:"attributes_list,omitempty" yaml:"attributes_list,omitempty" mapstructure:"attributes_list,omitempty"` // Detectors corresponds to the JSON schema field "detectors". Detectors *Detectors `json:"detectors,omitempty" yaml:"detectors,omitempty" mapstructure:"detectors,omitempty"` // SchemaUrl corresponds to the JSON schema field "schema_url". SchemaUrl *string `json:"schema_url,omitempty" yaml:"schema_url,omitempty" mapstructure:"schema_url,omitempty"` } type Sampler struct { // AlwaysOff corresponds to the JSON schema field "always_off". AlwaysOff SamplerAlwaysOff `json:"always_off,omitempty" yaml:"always_off,omitempty" mapstructure:"always_off,omitempty"` // AlwaysOn corresponds to the JSON schema field "always_on". AlwaysOn SamplerAlwaysOn `json:"always_on,omitempty" yaml:"always_on,omitempty" mapstructure:"always_on,omitempty"` // JaegerRemote corresponds to the JSON schema field "jaeger_remote". JaegerRemote *SamplerJaegerRemote `json:"jaeger_remote,omitempty" yaml:"jaeger_remote,omitempty" mapstructure:"jaeger_remote,omitempty"` // ParentBased corresponds to the JSON schema field "parent_based". ParentBased *SamplerParentBased `json:"parent_based,omitempty" yaml:"parent_based,omitempty" mapstructure:"parent_based,omitempty"` // TraceIDRatioBased corresponds to the JSON schema field "trace_id_ratio_based". TraceIDRatioBased *SamplerTraceIDRatioBased `json:"trace_id_ratio_based,omitempty" yaml:"trace_id_ratio_based,omitempty" mapstructure:"trace_id_ratio_based,omitempty"` AdditionalProperties interface{} } type SamplerAlwaysOff map[string]interface{} type SamplerAlwaysOn map[string]interface{} type SamplerJaegerRemote struct { // Endpoint corresponds to the JSON schema field "endpoint". Endpoint *string `json:"endpoint,omitempty" yaml:"endpoint,omitempty" mapstructure:"endpoint,omitempty"` // InitialSampler corresponds to the JSON schema field "initial_sampler". InitialSampler *Sampler `json:"initial_sampler,omitempty" yaml:"initial_sampler,omitempty" mapstructure:"initial_sampler,omitempty"` // Interval corresponds to the JSON schema field "interval". Interval *int `json:"interval,omitempty" yaml:"interval,omitempty" mapstructure:"interval,omitempty"` } type SamplerParentBased struct { // LocalParentNotSampled corresponds to the JSON schema field // "local_parent_not_sampled". LocalParentNotSampled *Sampler `json:"local_parent_not_sampled,omitempty" yaml:"local_parent_not_sampled,omitempty" mapstructure:"local_parent_not_sampled,omitempty"` // LocalParentSampled corresponds to the JSON schema field "local_parent_sampled". LocalParentSampled *Sampler `json:"local_parent_sampled,omitempty" yaml:"local_parent_sampled,omitempty" mapstructure:"local_parent_sampled,omitempty"` // RemoteParentNotSampled corresponds to the JSON schema field // "remote_parent_not_sampled". RemoteParentNotSampled *Sampler `json:"remote_parent_not_sampled,omitempty" yaml:"remote_parent_not_sampled,omitempty" mapstructure:"remote_parent_not_sampled,omitempty"` // RemoteParentSampled corresponds to the JSON schema field // "remote_parent_sampled". RemoteParentSampled *Sampler `json:"remote_parent_sampled,omitempty" yaml:"remote_parent_sampled,omitempty" mapstructure:"remote_parent_sampled,omitempty"` // Root corresponds to the JSON schema field "root". Root *Sampler `json:"root,omitempty" yaml:"root,omitempty" mapstructure:"root,omitempty"` } type SamplerTraceIDRatioBased struct { // Ratio corresponds to the JSON schema field "ratio". Ratio *float64 `json:"ratio,omitempty" yaml:"ratio,omitempty" mapstructure:"ratio,omitempty"` } type SimpleLogRecordProcessor struct { // Exporter corresponds to the JSON schema field "exporter". Exporter LogRecordExporter `json:"exporter" yaml:"exporter" mapstructure:"exporter"` } type SimpleSpanProcessor struct { // Exporter corresponds to the JSON schema field "exporter". Exporter SpanExporter `json:"exporter" yaml:"exporter" mapstructure:"exporter"` } type SpanExporter struct { // Console corresponds to the JSON schema field "console". Console Console `json:"console,omitempty" yaml:"console,omitempty" mapstructure:"console,omitempty"` // OTLP corresponds to the JSON schema field "otlp". OTLP *OTLP `json:"otlp,omitempty" yaml:"otlp,omitempty" mapstructure:"otlp,omitempty"` // Zipkin corresponds to the JSON schema field "zipkin". Zipkin *Zipkin `json:"zipkin,omitempty" yaml:"zipkin,omitempty" mapstructure:"zipkin,omitempty"` AdditionalProperties interface{} } type SpanLimits struct { // AttributeCountLimit corresponds to the JSON schema field // "attribute_count_limit". AttributeCountLimit *int `json:"attribute_count_limit,omitempty" yaml:"attribute_count_limit,omitempty" mapstructure:"attribute_count_limit,omitempty"` // AttributeValueLengthLimit corresponds to the JSON schema field // "attribute_value_length_limit". AttributeValueLengthLimit *int `json:"attribute_value_length_limit,omitempty" yaml:"attribute_value_length_limit,omitempty" mapstructure:"attribute_value_length_limit,omitempty"` // EventAttributeCountLimit corresponds to the JSON schema field // "event_attribute_count_limit". EventAttributeCountLimit *int `json:"event_attribute_count_limit,omitempty" yaml:"event_attribute_count_limit,omitempty" mapstructure:"event_attribute_count_limit,omitempty"` // EventCountLimit corresponds to the JSON schema field "event_count_limit". EventCountLimit *int `json:"event_count_limit,omitempty" yaml:"event_count_limit,omitempty" mapstructure:"event_count_limit,omitempty"` // LinkAttributeCountLimit corresponds to the JSON schema field // "link_attribute_count_limit". LinkAttributeCountLimit *int `json:"link_attribute_count_limit,omitempty" yaml:"link_attribute_count_limit,omitempty" mapstructure:"link_attribute_count_limit,omitempty"` // LinkCountLimit corresponds to the JSON schema field "link_count_limit". LinkCountLimit *int `json:"link_count_limit,omitempty" yaml:"link_count_limit,omitempty" mapstructure:"link_count_limit,omitempty"` } type SpanProcessor struct { // Batch corresponds to the JSON schema field "batch". Batch *BatchSpanProcessor `json:"batch,omitempty" yaml:"batch,omitempty" mapstructure:"batch,omitempty"` // Simple corresponds to the JSON schema field "simple". Simple *SimpleSpanProcessor `json:"simple,omitempty" yaml:"simple,omitempty" mapstructure:"simple,omitempty"` AdditionalProperties interface{} } type TracerProvider struct { // Limits corresponds to the JSON schema field "limits". Limits *SpanLimits `json:"limits,omitempty" yaml:"limits,omitempty" mapstructure:"limits,omitempty"` // Processors corresponds to the JSON schema field "processors". Processors []SpanProcessor `json:"processors,omitempty" yaml:"processors,omitempty" mapstructure:"processors,omitempty"` // Sampler corresponds to the JSON schema field "sampler". Sampler *Sampler `json:"sampler,omitempty" yaml:"sampler,omitempty" mapstructure:"sampler,omitempty"` } type View struct { // Selector corresponds to the JSON schema field "selector". Selector *ViewSelector `json:"selector,omitempty" yaml:"selector,omitempty" mapstructure:"selector,omitempty"` // Stream corresponds to the JSON schema field "stream". Stream *ViewStream `json:"stream,omitempty" yaml:"stream,omitempty" mapstructure:"stream,omitempty"` } type ViewSelector struct { // InstrumentName corresponds to the JSON schema field "instrument_name". InstrumentName *string `json:"instrument_name,omitempty" yaml:"instrument_name,omitempty" mapstructure:"instrument_name,omitempty"` // InstrumentType corresponds to the JSON schema field "instrument_type". InstrumentType *ViewSelectorInstrumentType `json:"instrument_type,omitempty" yaml:"instrument_type,omitempty" mapstructure:"instrument_type,omitempty"` // MeterName corresponds to the JSON schema field "meter_name". MeterName *string `json:"meter_name,omitempty" yaml:"meter_name,omitempty" mapstructure:"meter_name,omitempty"` // MeterSchemaUrl corresponds to the JSON schema field "meter_schema_url". MeterSchemaUrl *string `json:"meter_schema_url,omitempty" yaml:"meter_schema_url,omitempty" mapstructure:"meter_schema_url,omitempty"` // MeterVersion corresponds to the JSON schema field "meter_version". MeterVersion *string `json:"meter_version,omitempty" yaml:"meter_version,omitempty" mapstructure:"meter_version,omitempty"` // Unit corresponds to the JSON schema field "unit". Unit *string `json:"unit,omitempty" yaml:"unit,omitempty" mapstructure:"unit,omitempty"` } type ViewSelectorInstrumentType string const ViewSelectorInstrumentTypeCounter ViewSelectorInstrumentType = "counter" const ViewSelectorInstrumentTypeHistogram ViewSelectorInstrumentType = "histogram" const ViewSelectorInstrumentTypeObservableCounter ViewSelectorInstrumentType = "observable_counter" const ViewSelectorInstrumentTypeObservableGauge ViewSelectorInstrumentType = "observable_gauge" const ViewSelectorInstrumentTypeObservableUpDownCounter ViewSelectorInstrumentType = "observable_up_down_counter" const ViewSelectorInstrumentTypeUpDownCounter ViewSelectorInstrumentType = "up_down_counter" type ViewStream struct { // Aggregation corresponds to the JSON schema field "aggregation". Aggregation *ViewStreamAggregation `json:"aggregation,omitempty" yaml:"aggregation,omitempty" mapstructure:"aggregation,omitempty"` // AttributeKeys corresponds to the JSON schema field "attribute_keys". AttributeKeys *IncludeExclude `json:"attribute_keys,omitempty" yaml:"attribute_keys,omitempty" mapstructure:"attribute_keys,omitempty"` // Description corresponds to the JSON schema field "description". Description *string `json:"description,omitempty" yaml:"description,omitempty" mapstructure:"description,omitempty"` // Name corresponds to the JSON schema field "name". Name *string `json:"name,omitempty" yaml:"name,omitempty" mapstructure:"name,omitempty"` } type ViewStreamAggregation struct { // Base2ExponentialBucketHistogram corresponds to the JSON schema field // "base2_exponential_bucket_histogram". Base2ExponentialBucketHistogram *ViewStreamAggregationBase2ExponentialBucketHistogram `json:"base2_exponential_bucket_histogram,omitempty" yaml:"base2_exponential_bucket_histogram,omitempty" mapstructure:"base2_exponential_bucket_histogram,omitempty"` // Default corresponds to the JSON schema field "default". Default ViewStreamAggregationDefault `json:"default,omitempty" yaml:"default,omitempty" mapstructure:"default,omitempty"` // Drop corresponds to the JSON schema field "drop". Drop ViewStreamAggregationDrop `json:"drop,omitempty" yaml:"drop,omitempty" mapstructure:"drop,omitempty"` // ExplicitBucketHistogram corresponds to the JSON schema field // "explicit_bucket_histogram". ExplicitBucketHistogram *ViewStreamAggregationExplicitBucketHistogram `json:"explicit_bucket_histogram,omitempty" yaml:"explicit_bucket_histogram,omitempty" mapstructure:"explicit_bucket_histogram,omitempty"` // LastValue corresponds to the JSON schema field "last_value". LastValue ViewStreamAggregationLastValue `json:"last_value,omitempty" yaml:"last_value,omitempty" mapstructure:"last_value,omitempty"` // Sum corresponds to the JSON schema field "sum". Sum ViewStreamAggregationSum `json:"sum,omitempty" yaml:"sum,omitempty" mapstructure:"sum,omitempty"` } type ViewStreamAggregationBase2ExponentialBucketHistogram struct { // MaxScale corresponds to the JSON schema field "max_scale". MaxScale *int `json:"max_scale,omitempty" yaml:"max_scale,omitempty" mapstructure:"max_scale,omitempty"` // MaxSize corresponds to the JSON schema field "max_size". MaxSize *int `json:"max_size,omitempty" yaml:"max_size,omitempty" mapstructure:"max_size,omitempty"` // RecordMinMax corresponds to the JSON schema field "record_min_max". RecordMinMax *bool `json:"record_min_max,omitempty" yaml:"record_min_max,omitempty" mapstructure:"record_min_max,omitempty"` } type ViewStreamAggregationDefault map[string]interface{} type ViewStreamAggregationDrop map[string]interface{} type ViewStreamAggregationExplicitBucketHistogram struct { // Boundaries corresponds to the JSON schema field "boundaries". Boundaries []float64 `json:"boundaries,omitempty" yaml:"boundaries,omitempty" mapstructure:"boundaries,omitempty"` // RecordMinMax corresponds to the JSON schema field "record_min_max". RecordMinMax *bool `json:"record_min_max,omitempty" yaml:"record_min_max,omitempty" mapstructure:"record_min_max,omitempty"` } type ViewStreamAggregationLastValue map[string]interface{} type ViewStreamAggregationSum map[string]interface{} type Zipkin struct { // Endpoint corresponds to the JSON schema field "endpoint". Endpoint *string `json:"endpoint" yaml:"endpoint" mapstructure:"endpoint"` // Timeout corresponds to the JSON schema field "timeout". Timeout *int `json:"timeout,omitempty" yaml:"timeout,omitempty" mapstructure:"timeout,omitempty"` } open-telemetry-opentelemetry-go-contrib-e5abccb/config/go.mod000066400000000000000000000043651470323427300246340ustar00rootroot00000000000000module go.opentelemetry.io/contrib/config go 1.22 require ( github.com/prometheus/client_golang v1.20.4 github.com/stretchr/testify v1.9.0 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.7.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.31.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.31.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 go.opentelemetry.io/otel/exporters/prometheus v0.53.0 go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.7.0 go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.31.0 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0 go.opentelemetry.io/otel/log v0.7.0 go.opentelemetry.io/otel/metric v1.31.0 go.opentelemetry.io/otel/sdk v1.31.0 go.opentelemetry.io/otel/sdk/log v0.7.0 go.opentelemetry.io/otel/sdk/metric v1.31.0 go.opentelemetry.io/otel/trace v1.31.0 gopkg.in/yaml.v3 v3.0.1 ) require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect github.com/klauspost/compress v1.17.11 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.60.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect golang.org/x/net v0.30.0 // indirect golang.org/x/sys v0.26.0 // indirect golang.org/x/text v0.19.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 // indirect google.golang.org/grpc v1.67.1 // indirect google.golang.org/protobuf v1.35.1 // indirect ) open-telemetry-opentelemetry-go-contrib-e5abccb/config/go.sum000066400000000000000000000224611470323427300246560ustar00rootroot00000000000000github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI= github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.60.0 h1:+V9PAREWNvJMAuJ1x1BaWl9dewMW4YrHZQbx0sJNllA= github.com/prometheus/common v0.60.0/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.7.0 h1:mMOmtYie9Fx6TSVzw4W+NTpvoaS1JWWga37oI1a/4qQ= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.7.0/go.mod h1:yy7nDsMMBUkD+jeekJ36ur5f3jJIrmCwUrY67VFhNpA= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.31.0 h1:FZ6ei8GFW7kyPYdxJaV2rgI6M+4tvZzhYsQ2wgyVC08= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.31.0/go.mod h1:MdEu/mC6j3D+tTEfvI15b5Ci2Fn7NneJ71YMoiS3tpI= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.31.0 h1:ZsXq73BERAiNuuFXYqP4MR5hBrjXfMGSO+Cx7qoOZiM= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.31.0/go.mod h1:hg1zaDMpyZJuUzjFxFsRYBoccE86tM9Uf4IqNMUxvrY= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 h1:K0XaT3DwHAcV4nKLzcQvwAgSyisUghWoY20I7huthMk= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0/go.mod h1:B5Ki776z/MBnVha1Nzwp5arlzBbE3+1jk+pGmaP5HME= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0 h1:FFeLy03iVTXP6ffeN2iXrxfGsZGCjVx0/4KlizjyBwU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0/go.mod h1:TMu73/k1CP8nBUpDLc71Wj/Kf7ZS9FK5b53VapRsP9o= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 h1:lUsI2TYsQw2r1IASwoROaCnjdj2cvC2+Jbxvk6nHnWU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0/go.mod h1:2HpZxxQurfGxJlJDblybejHB6RX6pmExPNe517hREw4= go.opentelemetry.io/otel/exporters/prometheus v0.53.0 h1:QXobPHrwiGLM4ufrY3EOmDPJpo2P90UuFau4CDPJA/I= go.opentelemetry.io/otel/exporters/prometheus v0.53.0/go.mod h1:WOAXGr3D00CfzmFxtTV1eR0GpoHuPEu+HJT8UWW2SIU= go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.7.0 h1:TwmL3O3fRR80m8EshBrd8YydEZMcUCsZXzOUlnFohwM= go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.7.0/go.mod h1:tH98dDv5KPmPThswbXA0fr0Lwfs+OhK8HgaCo7PjRrk= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.31.0 h1:HZgBIps9wH0RDrwjrmNa3DVbNRW60HEhdzqZFyAp3fI= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.31.0/go.mod h1:RDRhvt6TDG0eIXmonAx5bd9IcwpqCkziwkOClzWKwAQ= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0 h1:UGZ1QwZWY67Z6BmckTU+9Rxn04m2bD3gD6Mk0OIOCPk= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0/go.mod h1:fcwWuDuaObkkChiDlhEpSq9+X1C0omv+s5mBtToAQ64= go.opentelemetry.io/otel/log v0.7.0 h1:d1abJc0b1QQZADKvfe9JqqrfmPYQCz2tUSO+0XZmuV4= go.opentelemetry.io/otel/log v0.7.0/go.mod h1:2jf2z7uVfnzDNknKTO9G+ahcOAyWcp1fJmk/wJjULRo= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/sdk/log v0.7.0 h1:dXkeI2S0MLc5g0/AwxTZv6EUEjctiH8aG14Am56NTmQ= go.opentelemetry.io/otel/sdk/log v0.7.0/go.mod h1:oIRXpW+WD6M8BuGj5rtS0aRu/86cbDV/dAfNaZBIjYM= go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc= go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 h1:T6rh4haD3GVYsgEfWExoCZA2o2FmbNyKpTuAxbEFPTg= google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:wp2WsuBYj6j8wUdo3ToZsdxxixbvQNAHqVJrTgi5E5M= google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 h1:QCqS/PdaHTSWGvupk2F/ehwHtGc0/GYkT+3GAcR1CCc= google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= open-telemetry-opentelemetry-go-contrib-e5abccb/config/jsonschema_patch.sed000066400000000000000000000002671470323427300275310ustar00rootroot00000000000000# go-jsonschema always generates patternProperties as # map[string]interface{}, for more specific types, they must # be replaced here s+type Headers.*+type Headers map[string]string+gopen-telemetry-opentelemetry-go-contrib-e5abccb/config/log.go000066400000000000000000000112671470323427300246350ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package config // import "go.opentelemetry.io/contrib/config" import ( "context" "errors" "fmt" "net/url" "time" "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp" "go.opentelemetry.io/otel/exporters/stdout/stdoutlog" "go.opentelemetry.io/otel/log" "go.opentelemetry.io/otel/log/noop" sdklog "go.opentelemetry.io/otel/sdk/log" "go.opentelemetry.io/otel/sdk/resource" ) func loggerProvider(cfg configOptions, res *resource.Resource) (log.LoggerProvider, shutdownFunc, error) { if cfg.opentelemetryConfig.LoggerProvider == nil { return noop.NewLoggerProvider(), noopShutdown, nil } opts := []sdklog.LoggerProviderOption{ sdklog.WithResource(res), } var errs []error for _, processor := range cfg.opentelemetryConfig.LoggerProvider.Processors { sp, err := logProcessor(cfg.ctx, processor) if err == nil { opts = append(opts, sdklog.WithProcessor(sp)) } else { errs = append(errs, err) } } if len(errs) > 0 { return noop.NewLoggerProvider(), noopShutdown, errors.Join(errs...) } lp := sdklog.NewLoggerProvider(opts...) return lp, lp.Shutdown, nil } func logProcessor(ctx context.Context, processor LogRecordProcessor) (sdklog.Processor, error) { if processor.Batch != nil && processor.Simple != nil { return nil, errors.New("must not specify multiple log processor type") } if processor.Batch != nil { exp, err := logExporter(ctx, processor.Batch.Exporter) if err != nil { return nil, err } return batchLogProcessor(processor.Batch, exp) } if processor.Simple != nil { exp, err := logExporter(ctx, processor.Simple.Exporter) if err != nil { return nil, err } return sdklog.NewSimpleProcessor(exp), nil } return nil, fmt.Errorf("unsupported log processor type, must be one of simple or batch") } func logExporter(ctx context.Context, exporter LogRecordExporter) (sdklog.Exporter, error) { if exporter.Console != nil && exporter.OTLP != nil { return nil, errors.New("must not specify multiple exporters") } if exporter.Console != nil { return stdoutlog.New( stdoutlog.WithPrettyPrint(), ) } if exporter.OTLP != nil && exporter.OTLP.Protocol != nil { switch *exporter.OTLP.Protocol { case protocolProtobufHTTP: return otlpHTTPLogExporter(ctx, exporter.OTLP) default: return nil, fmt.Errorf("unsupported protocol %q", *exporter.OTLP.Protocol) } } return nil, errors.New("no valid log exporter") } func batchLogProcessor(blp *BatchLogRecordProcessor, exp sdklog.Exporter) (*sdklog.BatchProcessor, error) { var opts []sdklog.BatchProcessorOption if blp.ExportTimeout != nil { if *blp.ExportTimeout < 0 { return nil, fmt.Errorf("invalid export timeout %d", *blp.ExportTimeout) } opts = append(opts, sdklog.WithExportTimeout(time.Millisecond*time.Duration(*blp.ExportTimeout))) } if blp.MaxExportBatchSize != nil { if *blp.MaxExportBatchSize < 0 { return nil, fmt.Errorf("invalid batch size %d", *blp.MaxExportBatchSize) } opts = append(opts, sdklog.WithExportMaxBatchSize(*blp.MaxExportBatchSize)) } if blp.MaxQueueSize != nil { if *blp.MaxQueueSize < 0 { return nil, fmt.Errorf("invalid queue size %d", *blp.MaxQueueSize) } opts = append(opts, sdklog.WithMaxQueueSize(*blp.MaxQueueSize)) } if blp.ScheduleDelay != nil { if *blp.ScheduleDelay < 0 { return nil, fmt.Errorf("invalid schedule delay %d", *blp.ScheduleDelay) } opts = append(opts, sdklog.WithExportInterval(time.Millisecond*time.Duration(*blp.ScheduleDelay))) } return sdklog.NewBatchProcessor(exp, opts...), nil } func otlpHTTPLogExporter(ctx context.Context, otlpConfig *OTLP) (sdklog.Exporter, error) { var opts []otlploghttp.Option if otlpConfig.Endpoint != nil { u, err := url.ParseRequestURI(*otlpConfig.Endpoint) if err != nil { return nil, err } opts = append(opts, otlploghttp.WithEndpoint(u.Host)) if u.Scheme == "http" { opts = append(opts, otlploghttp.WithInsecure()) } if len(u.Path) > 0 { opts = append(opts, otlploghttp.WithURLPath(u.Path)) } } if otlpConfig.Compression != nil { switch *otlpConfig.Compression { case compressionGzip: opts = append(opts, otlploghttp.WithCompression(otlploghttp.GzipCompression)) case compressionNone: opts = append(opts, otlploghttp.WithCompression(otlploghttp.NoCompression)) default: return nil, fmt.Errorf("unsupported compression %q", *otlpConfig.Compression) } } if otlpConfig.Timeout != nil && *otlpConfig.Timeout > 0 { opts = append(opts, otlploghttp.WithTimeout(time.Millisecond*time.Duration(*otlpConfig.Timeout))) } if len(otlpConfig.Headers) > 0 { opts = append(opts, otlploghttp.WithHeaders(toStringMap(otlpConfig.Headers))) } return otlploghttp.New(ctx, opts...) } open-telemetry-opentelemetry-go-contrib-e5abccb/config/log_test.go000066400000000000000000000255341470323427300256760ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package config // import "go.opentelemetry.io/contrib/config" import ( "context" "errors" "net/url" "reflect" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp" "go.opentelemetry.io/otel/exporters/stdout/stdoutlog" "go.opentelemetry.io/otel/log" "go.opentelemetry.io/otel/log/noop" sdklog "go.opentelemetry.io/otel/sdk/log" "go.opentelemetry.io/otel/sdk/resource" ) func TestLoggerProvider(t *testing.T) { tests := []struct { name string cfg configOptions wantProvider log.LoggerProvider wantErr error }{ { name: "no-logger-provider-configured", wantProvider: noop.NewLoggerProvider(), }, { name: "error-in-config", cfg: configOptions{ opentelemetryConfig: OpenTelemetryConfiguration{ LoggerProvider: &LoggerProvider{ Processors: []LogRecordProcessor{ { Simple: &SimpleLogRecordProcessor{}, Batch: &BatchLogRecordProcessor{}, }, }, }, }, }, wantProvider: noop.NewLoggerProvider(), wantErr: errors.Join(errors.New("must not specify multiple log processor type")), }, } for _, tt := range tests { mp, shutdown, err := loggerProvider(tt.cfg, resource.Default()) require.Equal(t, tt.wantProvider, mp) assert.Equal(t, tt.wantErr, err) require.NoError(t, shutdown(context.Background())) } } func TestLogProcessor(t *testing.T) { ctx := context.Background() otlpHTTPExporter, err := otlploghttp.New(ctx) require.NoError(t, err) consoleExporter, err := stdoutlog.New( stdoutlog.WithPrettyPrint(), ) require.NoError(t, err) testCases := []struct { name string processor LogRecordProcessor args any wantErr error wantProcessor sdklog.Processor }{ { name: "no processor", wantErr: errors.New("unsupported log processor type, must be one of simple or batch"), }, { name: "multiple processor types", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ Exporter: LogRecordExporter{}, }, Simple: &SimpleLogRecordProcessor{}, }, wantErr: errors.New("must not specify multiple log processor type"), }, { name: "batch processor invalid batch size otlphttp exporter", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxExportBatchSize: ptr(-1), Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: ptr("http/protobuf"), }, }, }, }, wantErr: errors.New("invalid batch size -1"), }, { name: "batch processor invalid export timeout otlphttp exporter", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ ExportTimeout: ptr(-2), Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: ptr("http/protobuf"), }, }, }, }, wantErr: errors.New("invalid export timeout -2"), }, { name: "batch processor invalid queue size otlphttp exporter", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxQueueSize: ptr(-3), Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: ptr("http/protobuf"), }, }, }, }, wantErr: errors.New("invalid queue size -3"), }, { name: "batch processor invalid schedule delay console exporter", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ ScheduleDelay: ptr(-4), Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: ptr("http/protobuf"), }, }, }, }, wantErr: errors.New("invalid schedule delay -4"), }, { name: "batch processor invalid exporter", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ Exporter: LogRecordExporter{}, }, }, wantErr: errors.New("no valid log exporter"), }, { name: "batch/console", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: LogRecordExporter{ Console: map[string]any{}, }, }, }, wantProcessor: sdklog.NewBatchProcessor(consoleExporter), }, { name: "batch/otlp-http-exporter", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: ptr("http/protobuf"), Endpoint: ptr("http://localhost:4318"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantProcessor: sdklog.NewBatchProcessor(otlpHTTPExporter), }, { name: "batch/otlp-http-exporter-with-path", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: ptr("http/protobuf"), Endpoint: ptr("http://localhost:4318/path/123"), Compression: ptr("none"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantProcessor: sdklog.NewBatchProcessor(otlpHTTPExporter), }, { name: "batch/otlp-http-exporter-no-endpoint", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: ptr("http/protobuf"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantProcessor: sdklog.NewBatchProcessor(otlpHTTPExporter), }, { name: "batch/otlp-http-exporter-no-scheme", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: ptr("http/protobuf"), Endpoint: ptr("localhost:4318"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantProcessor: sdklog.NewBatchProcessor(otlpHTTPExporter), }, { name: "batch/otlp-http-invalid-protocol", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: ptr("invalid"), Endpoint: ptr("https://10.0.0.0:443"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantErr: errors.New("unsupported protocol \"invalid\""), }, { name: "batch/otlp-http-invalid-endpoint", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: ptr("http/protobuf"), Endpoint: ptr(" "), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantErr: &url.Error{Op: "parse", URL: " ", Err: errors.New("invalid URI for request")}, }, { name: "batch/otlp-http-none-compression", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: ptr("http/protobuf"), Endpoint: ptr("localhost:4318"), Compression: ptr("none"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantProcessor: sdklog.NewBatchProcessor(otlpHTTPExporter), }, { name: "batch/otlp-http-invalid-compression", processor: LogRecordProcessor{ Batch: &BatchLogRecordProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: ptr("http/protobuf"), Endpoint: ptr("localhost:4318"), Compression: ptr("invalid"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantErr: errors.New("unsupported compression \"invalid\""), }, { name: "simple/no-exporter", processor: LogRecordProcessor{ Simple: &SimpleLogRecordProcessor{ Exporter: LogRecordExporter{}, }, }, wantErr: errors.New("no valid log exporter"), }, { name: "simple/console", processor: LogRecordProcessor{ Simple: &SimpleLogRecordProcessor{ Exporter: LogRecordExporter{ Console: map[string]any{}, }, }, }, wantProcessor: sdklog.NewSimpleProcessor(consoleExporter), }, { name: "simple/otlp-exporter", processor: LogRecordProcessor{ Simple: &SimpleLogRecordProcessor{ Exporter: LogRecordExporter{ OTLP: &OTLP{ Protocol: ptr("http/protobuf"), Endpoint: ptr("localhost:4318"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantProcessor: sdklog.NewSimpleProcessor(otlpHTTPExporter), }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { got, err := logProcessor(context.Background(), tt.processor) require.Equal(t, tt.wantErr, err) if tt.wantProcessor == nil { require.Nil(t, got) } else { require.Equal(t, reflect.TypeOf(tt.wantProcessor), reflect.TypeOf(got)) wantExporterType := reflect.Indirect(reflect.ValueOf(tt.wantProcessor)).FieldByName("exporter").Elem().Type() gotExporterType := reflect.Indirect(reflect.ValueOf(got)).FieldByName("exporter").Elem().Type() require.Equal(t, wantExporterType.String(), gotExporterType.String()) } }) } } open-telemetry-opentelemetry-go-contrib-e5abccb/config/metric.go000066400000000000000000000366521470323427300253440ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package config // import "go.opentelemetry.io/contrib/config" import ( "context" "encoding/json" "errors" "fmt" "math" "net" "net/http" "net/url" "os" "time" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" otelprom "go.opentelemetry.io/otel/exporters/prometheus" "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/metric/noop" "go.opentelemetry.io/otel/sdk/instrumentation" sdkmetric "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/resource" ) var zeroScope instrumentation.Scope const instrumentKindUndefined = sdkmetric.InstrumentKind(0) func meterProvider(cfg configOptions, res *resource.Resource) (metric.MeterProvider, shutdownFunc, error) { if cfg.opentelemetryConfig.MeterProvider == nil { return noop.NewMeterProvider(), noopShutdown, nil } opts := []sdkmetric.Option{ sdkmetric.WithResource(res), } var errs []error for _, reader := range cfg.opentelemetryConfig.MeterProvider.Readers { r, err := metricReader(cfg.ctx, reader) if err == nil { opts = append(opts, sdkmetric.WithReader(r)) } else { errs = append(errs, err) } } for _, vw := range cfg.opentelemetryConfig.MeterProvider.Views { v, err := view(vw) if err == nil { opts = append(opts, sdkmetric.WithView(v)) } else { errs = append(errs, err) } } if len(errs) > 0 { return noop.NewMeterProvider(), noopShutdown, errors.Join(errs...) } mp := sdkmetric.NewMeterProvider(opts...) return mp, mp.Shutdown, nil } func metricReader(ctx context.Context, r MetricReader) (sdkmetric.Reader, error) { if r.Periodic != nil && r.Pull != nil { return nil, errors.New("must not specify multiple metric reader type") } if r.Periodic != nil { var opts []sdkmetric.PeriodicReaderOption if r.Periodic.Interval != nil { opts = append(opts, sdkmetric.WithInterval(time.Duration(*r.Periodic.Interval)*time.Millisecond)) } if r.Periodic.Timeout != nil { opts = append(opts, sdkmetric.WithTimeout(time.Duration(*r.Periodic.Timeout)*time.Millisecond)) } return periodicExporter(ctx, r.Periodic.Exporter, opts...) } if r.Pull != nil { return pullReader(ctx, r.Pull.Exporter) } return nil, errors.New("no valid metric reader") } func pullReader(ctx context.Context, exporter PullMetricExporter) (sdkmetric.Reader, error) { if exporter.Prometheus != nil { return prometheusReader(ctx, exporter.Prometheus) } return nil, errors.New("no valid metric exporter") } func periodicExporter(ctx context.Context, exporter PushMetricExporter, opts ...sdkmetric.PeriodicReaderOption) (sdkmetric.Reader, error) { if exporter.Console != nil && exporter.OTLP != nil { return nil, errors.New("must not specify multiple exporters") } if exporter.Console != nil { enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") exp, err := stdoutmetric.New( stdoutmetric.WithEncoder(enc), ) if err != nil { return nil, err } return sdkmetric.NewPeriodicReader(exp, opts...), nil } if exporter.OTLP != nil && exporter.OTLP.Protocol != nil { var err error var exp sdkmetric.Exporter switch *exporter.OTLP.Protocol { case protocolProtobufHTTP: exp, err = otlpHTTPMetricExporter(ctx, exporter.OTLP) case protocolProtobufGRPC: exp, err = otlpGRPCMetricExporter(ctx, exporter.OTLP) default: return nil, fmt.Errorf("unsupported protocol %q", *exporter.OTLP.Protocol) } if err != nil { return nil, err } return sdkmetric.NewPeriodicReader(exp, opts...), nil } return nil, errors.New("no valid metric exporter") } func otlpHTTPMetricExporter(ctx context.Context, otlpConfig *OTLPMetric) (sdkmetric.Exporter, error) { opts := []otlpmetrichttp.Option{} if otlpConfig.Endpoint != nil { u, err := url.ParseRequestURI(*otlpConfig.Endpoint) if err != nil { return nil, err } opts = append(opts, otlpmetrichttp.WithEndpoint(u.Host)) if u.Scheme == "http" { opts = append(opts, otlpmetrichttp.WithInsecure()) } if len(u.Path) > 0 { opts = append(opts, otlpmetrichttp.WithURLPath(u.Path)) } } if otlpConfig.Compression != nil { switch *otlpConfig.Compression { case compressionGzip: opts = append(opts, otlpmetrichttp.WithCompression(otlpmetrichttp.GzipCompression)) case compressionNone: opts = append(opts, otlpmetrichttp.WithCompression(otlpmetrichttp.NoCompression)) default: return nil, fmt.Errorf("unsupported compression %q", *otlpConfig.Compression) } } if otlpConfig.Timeout != nil { opts = append(opts, otlpmetrichttp.WithTimeout(time.Millisecond*time.Duration(*otlpConfig.Timeout))) } if len(otlpConfig.Headers) > 0 { opts = append(opts, otlpmetrichttp.WithHeaders(toStringMap(otlpConfig.Headers))) } if otlpConfig.TemporalityPreference != nil { switch *otlpConfig.TemporalityPreference { case "delta": opts = append(opts, otlpmetrichttp.WithTemporalitySelector(deltaTemporality)) case "cumulative": opts = append(opts, otlpmetrichttp.WithTemporalitySelector(cumulativeTemporality)) case "lowmemory": opts = append(opts, otlpmetrichttp.WithTemporalitySelector(lowMemory)) default: return nil, fmt.Errorf("unsupported temporality preference %q", *otlpConfig.TemporalityPreference) } } return otlpmetrichttp.New(ctx, opts...) } func otlpGRPCMetricExporter(ctx context.Context, otlpConfig *OTLPMetric) (sdkmetric.Exporter, error) { var opts []otlpmetricgrpc.Option if otlpConfig.Endpoint != nil { u, err := url.ParseRequestURI(*otlpConfig.Endpoint) if err != nil { return nil, err } // ParseRequestURI leaves the Host field empty when no // scheme is specified (i.e. localhost:4317). This check is // here to support the case where a user may not specify a // scheme. The code does its best effort here by using // otlpConfig.Endpoint as-is in that case if u.Host != "" { opts = append(opts, otlpmetricgrpc.WithEndpoint(u.Host)) } else { opts = append(opts, otlpmetricgrpc.WithEndpoint(*otlpConfig.Endpoint)) } if u.Scheme == "http" { opts = append(opts, otlpmetricgrpc.WithInsecure()) } } if otlpConfig.Compression != nil { switch *otlpConfig.Compression { case compressionGzip: opts = append(opts, otlpmetricgrpc.WithCompressor(*otlpConfig.Compression)) case compressionNone: // none requires no options default: return nil, fmt.Errorf("unsupported compression %q", *otlpConfig.Compression) } } if otlpConfig.Timeout != nil && *otlpConfig.Timeout > 0 { opts = append(opts, otlpmetricgrpc.WithTimeout(time.Millisecond*time.Duration(*otlpConfig.Timeout))) } if len(otlpConfig.Headers) > 0 { opts = append(opts, otlpmetricgrpc.WithHeaders(toStringMap(otlpConfig.Headers))) } if otlpConfig.TemporalityPreference != nil { switch *otlpConfig.TemporalityPreference { case "delta": opts = append(opts, otlpmetricgrpc.WithTemporalitySelector(deltaTemporality)) case "cumulative": opts = append(opts, otlpmetricgrpc.WithTemporalitySelector(cumulativeTemporality)) case "lowmemory": opts = append(opts, otlpmetricgrpc.WithTemporalitySelector(lowMemory)) default: return nil, fmt.Errorf("unsupported temporality preference %q", *otlpConfig.TemporalityPreference) } } return otlpmetricgrpc.New(ctx, opts...) } func cumulativeTemporality(sdkmetric.InstrumentKind) metricdata.Temporality { return metricdata.CumulativeTemporality } func deltaTemporality(ik sdkmetric.InstrumentKind) metricdata.Temporality { switch ik { case sdkmetric.InstrumentKindCounter, sdkmetric.InstrumentKindHistogram, sdkmetric.InstrumentKindObservableCounter: return metricdata.DeltaTemporality default: return metricdata.CumulativeTemporality } } func lowMemory(ik sdkmetric.InstrumentKind) metricdata.Temporality { switch ik { case sdkmetric.InstrumentKindCounter, sdkmetric.InstrumentKindHistogram: return metricdata.DeltaTemporality default: return metricdata.CumulativeTemporality } } // newIncludeExcludeFilter returns a Filter that includes attributes // in the include list and excludes attributes in the excludes list. // It returns an error if an attribute is in both lists // // If IncludeExclude is empty a include-all filter is returned. func newIncludeExcludeFilter(lists *IncludeExclude) (attribute.Filter, error) { if lists == nil { return func(kv attribute.KeyValue) bool { return true }, nil } included := make(map[attribute.Key]struct{}) for _, k := range lists.Included { included[attribute.Key(k)] = struct{}{} } excluded := make(map[attribute.Key]struct{}) for _, k := range lists.Excluded { if _, ok := included[attribute.Key(k)]; ok { return nil, fmt.Errorf("attribute cannot be in both include and exclude list: %s", k) } excluded[attribute.Key(k)] = struct{}{} } return func(kv attribute.KeyValue) bool { // check if a value is excluded first if _, ok := excluded[kv.Key]; ok { return false } if len(included) == 0 { return true } _, ok := included[kv.Key] return ok }, nil } func prometheusReader(ctx context.Context, prometheusConfig *Prometheus) (sdkmetric.Reader, error) { var opts []otelprom.Option if prometheusConfig.Host == nil { return nil, fmt.Errorf("host must be specified") } if prometheusConfig.Port == nil { return nil, fmt.Errorf("port must be specified") } if prometheusConfig.WithoutScopeInfo != nil && *prometheusConfig.WithoutScopeInfo { opts = append(opts, otelprom.WithoutScopeInfo()) } if prometheusConfig.WithoutTypeSuffix != nil && *prometheusConfig.WithoutTypeSuffix { opts = append(opts, otelprom.WithoutCounterSuffixes()) } if prometheusConfig.WithoutUnits != nil && *prometheusConfig.WithoutUnits { opts = append(opts, otelprom.WithoutUnits()) } if prometheusConfig.WithResourceConstantLabels != nil { f, err := newIncludeExcludeFilter(prometheusConfig.WithResourceConstantLabels) if err != nil { return nil, err } otelprom.WithResourceAsConstantLabels(f) } reg := prometheus.NewRegistry() opts = append(opts, otelprom.WithRegisterer(reg)) mux := http.NewServeMux() mux.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{Registry: reg})) server := http.Server{ // Timeouts are necessary to make a server resilient to attacks, but ListenAndServe doesn't set any. // We use values from this example: https://blog.cloudflare.com/exposing-go-on-the-internet/#:~:text=There%20are%20three%20main%20timeouts ReadTimeout: 5 * time.Second, WriteTimeout: 10 * time.Second, IdleTimeout: 120 * time.Second, Handler: mux, } addr := fmt.Sprintf("%s:%d", *prometheusConfig.Host, *prometheusConfig.Port) reader, err := otelprom.New(opts...) if err != nil { return nil, fmt.Errorf("error creating otel prometheus exporter: %w", err) } lis, err := net.Listen("tcp", addr) if err != nil { return nil, errors.Join( fmt.Errorf("binding address %s for Prometheus exporter: %w", addr, err), reader.Shutdown(ctx), ) } go func() { if err := server.Serve(lis); err != nil && errors.Is(err, http.ErrServerClosed) { otel.Handle(fmt.Errorf("the Prometheus HTTP server exited unexpectedly: %w", err)) } }() return readerWithServer{reader, &server}, nil } type readerWithServer struct { sdkmetric.Reader server *http.Server } func (rws readerWithServer) Shutdown(ctx context.Context) error { return errors.Join( rws.Reader.Shutdown(ctx), rws.server.Shutdown(ctx), ) } func view(v View) (sdkmetric.View, error) { if v.Selector == nil { return nil, errors.New("view: no selector provided") } inst, err := instrument(*v.Selector) if err != nil { return nil, err } s, err := stream(v.Stream) if err != nil { return nil, err } return sdkmetric.NewView(inst, s), nil } func instrument(vs ViewSelector) (sdkmetric.Instrument, error) { kind, err := instrumentKind(vs.InstrumentType) if err != nil { return sdkmetric.Instrument{}, fmt.Errorf("view_selector: %w", err) } inst := sdkmetric.Instrument{ Name: strOrEmpty(vs.InstrumentName), Unit: strOrEmpty(vs.Unit), Kind: kind, Scope: instrumentation.Scope{ Name: strOrEmpty(vs.MeterName), Version: strOrEmpty(vs.MeterVersion), SchemaURL: strOrEmpty(vs.MeterSchemaUrl), }, } if instrumentIsEmpty(inst) { return sdkmetric.Instrument{}, errors.New("view_selector: empty selector not supporter") } return inst, nil } func stream(vs *ViewStream) (sdkmetric.Stream, error) { if vs == nil { return sdkmetric.Stream{}, nil } f, err := newIncludeExcludeFilter(vs.AttributeKeys) if err != nil { return sdkmetric.Stream{}, err } return sdkmetric.Stream{ Name: strOrEmpty(vs.Name), Description: strOrEmpty(vs.Description), Aggregation: aggregation(vs.Aggregation), AttributeFilter: f, }, nil } func aggregation(aggr *ViewStreamAggregation) sdkmetric.Aggregation { if aggr == nil { return nil } if aggr.Base2ExponentialBucketHistogram != nil { return sdkmetric.AggregationBase2ExponentialHistogram{ MaxSize: int32OrZero(aggr.Base2ExponentialBucketHistogram.MaxSize), MaxScale: int32OrZero(aggr.Base2ExponentialBucketHistogram.MaxScale), // Need to negate because config has the positive action RecordMinMax. NoMinMax: !boolOrFalse(aggr.Base2ExponentialBucketHistogram.RecordMinMax), } } if aggr.Default != nil { // TODO: Understand what to set here. return nil } if aggr.Drop != nil { return sdkmetric.AggregationDrop{} } if aggr.ExplicitBucketHistogram != nil { return sdkmetric.AggregationExplicitBucketHistogram{ Boundaries: aggr.ExplicitBucketHistogram.Boundaries, // Need to negate because config has the positive action RecordMinMax. NoMinMax: !boolOrFalse(aggr.ExplicitBucketHistogram.RecordMinMax), } } if aggr.LastValue != nil { return sdkmetric.AggregationLastValue{} } if aggr.Sum != nil { return sdkmetric.AggregationSum{} } return nil } func instrumentKind(vsit *ViewSelectorInstrumentType) (sdkmetric.InstrumentKind, error) { if vsit == nil { // Equivalent to instrumentKindUndefined. return instrumentKindUndefined, nil } switch *vsit { case ViewSelectorInstrumentTypeCounter: return sdkmetric.InstrumentKindCounter, nil case ViewSelectorInstrumentTypeUpDownCounter: return sdkmetric.InstrumentKindUpDownCounter, nil case ViewSelectorInstrumentTypeHistogram: return sdkmetric.InstrumentKindHistogram, nil case ViewSelectorInstrumentTypeObservableCounter: return sdkmetric.InstrumentKindObservableCounter, nil case ViewSelectorInstrumentTypeObservableUpDownCounter: return sdkmetric.InstrumentKindObservableUpDownCounter, nil case ViewSelectorInstrumentTypeObservableGauge: return sdkmetric.InstrumentKindObservableGauge, nil } return instrumentKindUndefined, errors.New("instrument_type: invalid value") } func instrumentIsEmpty(i sdkmetric.Instrument) bool { return i.Name == "" && i.Description == "" && i.Kind == instrumentKindUndefined && i.Unit == "" && i.Scope == zeroScope } func boolOrFalse(pBool *bool) bool { if pBool == nil { return false } return *pBool } func int32OrZero(pInt *int) int32 { if pInt == nil { return 0 } i := *pInt if i > math.MaxInt32 { return math.MaxInt32 } if i < math.MinInt32 { return math.MinInt32 } return int32(i) // nolint: gosec // Overflow and underflow checked above. } func strOrEmpty(pStr *string) string { if pStr == nil { return "" } return *pStr } open-telemetry-opentelemetry-go-contrib-e5abccb/config/metric_test.go000066400000000000000000000746151470323427300264040ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package config import ( "context" "errors" "fmt" "net/url" "reflect" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" otelprom "go.opentelemetry.io/otel/exporters/prometheus" "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/metric/noop" "go.opentelemetry.io/otel/sdk/instrumentation" sdkmetric "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/resource" ) func TestMeterProvider(t *testing.T) { tests := []struct { name string cfg configOptions wantProvider metric.MeterProvider wantErr error }{ { name: "no-meter-provider-configured", wantProvider: noop.NewMeterProvider(), }, { name: "error-in-config", cfg: configOptions{ opentelemetryConfig: OpenTelemetryConfiguration{ MeterProvider: &MeterProvider{ Readers: []MetricReader{ { Periodic: &PeriodicMetricReader{}, Pull: &PullMetricReader{}, }, }, }, }, }, wantProvider: noop.NewMeterProvider(), wantErr: errors.Join(errors.New("must not specify multiple metric reader type")), }, { name: "multiple-errors-in-config", cfg: configOptions{ opentelemetryConfig: OpenTelemetryConfiguration{ MeterProvider: &MeterProvider{ Readers: []MetricReader{ { Periodic: &PeriodicMetricReader{}, Pull: &PullMetricReader{}, }, { Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ Console: Console{}, OTLP: &OTLPMetric{}, }, }, }, }, }, }, }, wantProvider: noop.NewMeterProvider(), wantErr: errors.Join(errors.New("must not specify multiple metric reader type"), errors.New("must not specify multiple exporters")), }, } for _, tt := range tests { mp, shutdown, err := meterProvider(tt.cfg, resource.Default()) require.Equal(t, tt.wantProvider, mp) assert.Equal(t, tt.wantErr, err) require.NoError(t, shutdown(context.Background())) } } func TestReader(t *testing.T) { consoleExporter, err := stdoutmetric.New( stdoutmetric.WithPrettyPrint(), ) require.NoError(t, err) ctx := context.Background() otlpGRPCExporter, err := otlpmetricgrpc.New(ctx) require.NoError(t, err) otlpHTTPExporter, err := otlpmetrichttp.New(ctx) require.NoError(t, err) promExporter, err := otelprom.New() require.NoError(t, err) testCases := []struct { name string reader MetricReader args any wantErr error wantReader sdkmetric.Reader }{ { name: "no reader", wantErr: errors.New("no valid metric reader"), }, { name: "pull/no-exporter", reader: MetricReader{ Pull: &PullMetricReader{}, }, wantErr: errors.New("no valid metric exporter"), }, { name: "pull/prometheus-no-host", reader: MetricReader{ Pull: &PullMetricReader{ Exporter: PullMetricExporter{ Prometheus: &Prometheus{}, }, }, }, wantErr: errors.New("host must be specified"), }, { name: "pull/prometheus-no-port", reader: MetricReader{ Pull: &PullMetricReader{ Exporter: PullMetricExporter{ Prometheus: &Prometheus{ Host: ptr("localhost"), }, }, }, }, wantErr: errors.New("port must be specified"), }, { name: "pull/prometheus", reader: MetricReader{ Pull: &PullMetricReader{ Exporter: PullMetricExporter{ Prometheus: &Prometheus{ Host: ptr("localhost"), Port: ptr(8888), WithoutScopeInfo: ptr(true), WithoutUnits: ptr(true), WithoutTypeSuffix: ptr(true), WithResourceConstantLabels: &IncludeExclude{ Included: []string{"include"}, Excluded: []string{"exclude"}, }, }, }, }, }, wantReader: readerWithServer{promExporter, nil}, }, { name: "periodic/otlp-exporter-invalid-protocol", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("http/invalid"), }, }, }, }, wantErr: errors.New("unsupported protocol \"http/invalid\""), }, { name: "periodic/otlp-grpc-exporter", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("grpc/protobuf"), Endpoint: ptr("http://localhost:4318"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpGRPCExporter), }, { name: "periodic/otlp-grpc-exporter-with-path", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("grpc/protobuf"), Endpoint: ptr("http://localhost:4318/path/123"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpGRPCExporter), }, { name: "periodic/otlp-grpc-exporter-no-endpoint", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("grpc/protobuf"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpGRPCExporter), }, { name: "periodic/otlp-grpc-exporter-no-scheme", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("grpc/protobuf"), Endpoint: ptr("localhost:4318"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpGRPCExporter), }, { name: "periodic/otlp-grpc-invalid-endpoint", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("grpc/protobuf"), Endpoint: ptr(" "), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantErr: &url.Error{Op: "parse", URL: " ", Err: errors.New("invalid URI for request")}, }, { name: "periodic/otlp-grpc-none-compression", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("grpc/protobuf"), Endpoint: ptr("localhost:4318"), Compression: ptr("none"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpGRPCExporter), }, { name: "periodic/otlp-grpc-delta-temporality", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("grpc/protobuf"), Endpoint: ptr("localhost:4318"), Compression: ptr("none"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, TemporalityPreference: ptr("delta"), }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpGRPCExporter), }, { name: "periodic/otlp-grpc-cumulative-temporality", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("grpc/protobuf"), Endpoint: ptr("localhost:4318"), Compression: ptr("none"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, TemporalityPreference: ptr("cumulative"), }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpGRPCExporter), }, { name: "periodic/otlp-grpc-lowmemory-temporality", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("grpc/protobuf"), Endpoint: ptr("localhost:4318"), Compression: ptr("none"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, TemporalityPreference: ptr("lowmemory"), }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpGRPCExporter), }, { name: "periodic/otlp-grpc-invalid-temporality", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("grpc/protobuf"), Endpoint: ptr("localhost:4318"), Compression: ptr("none"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, TemporalityPreference: ptr("invalid"), }, }, }, }, wantErr: errors.New("unsupported temporality preference \"invalid\""), }, { name: "periodic/otlp-grpc-invalid-compression", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("grpc/protobuf"), Endpoint: ptr("localhost:4318"), Compression: ptr("invalid"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantErr: errors.New("unsupported compression \"invalid\""), }, { name: "periodic/otlp-http-exporter", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("http/protobuf"), Endpoint: ptr("http://localhost:4318"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpHTTPExporter), }, { name: "periodic/otlp-http-exporter-with-path", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("http/protobuf"), Endpoint: ptr("http://localhost:4318/path/123"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpHTTPExporter), }, { name: "periodic/otlp-http-exporter-no-endpoint", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("http/protobuf"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpHTTPExporter), }, { name: "periodic/otlp-http-exporter-no-scheme", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("http/protobuf"), Endpoint: ptr("localhost:4318"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpHTTPExporter), }, { name: "periodic/otlp-http-invalid-endpoint", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("http/protobuf"), Endpoint: ptr(" "), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantErr: &url.Error{Op: "parse", URL: " ", Err: errors.New("invalid URI for request")}, }, { name: "periodic/otlp-http-none-compression", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("http/protobuf"), Endpoint: ptr("localhost:4318"), Compression: ptr("none"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpHTTPExporter), }, { name: "periodic/otlp-http-cumulative-temporality", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("http/protobuf"), Endpoint: ptr("localhost:4318"), Compression: ptr("none"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, TemporalityPreference: ptr("cumulative"), }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpHTTPExporter), }, { name: "periodic/otlp-http-lowmemory-temporality", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("http/protobuf"), Endpoint: ptr("localhost:4318"), Compression: ptr("none"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, TemporalityPreference: ptr("lowmemory"), }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpHTTPExporter), }, { name: "periodic/otlp-http-delta-temporality", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("http/protobuf"), Endpoint: ptr("localhost:4318"), Compression: ptr("none"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, TemporalityPreference: ptr("delta"), }, }, }, }, wantReader: sdkmetric.NewPeriodicReader(otlpHTTPExporter), }, { name: "periodic/otlp-http-invalid-temporality", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("http/protobuf"), Endpoint: ptr("localhost:4318"), Compression: ptr("none"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, TemporalityPreference: ptr("invalid"), }, }, }, }, wantErr: errors.New("unsupported temporality preference \"invalid\""), }, { name: "periodic/otlp-http-invalid-compression", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ OTLP: &OTLPMetric{ Protocol: ptr("http/protobuf"), Endpoint: ptr("localhost:4318"), Compression: ptr("invalid"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantErr: errors.New("unsupported compression \"invalid\""), }, { name: "periodic/no-exporter", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{}, }, }, wantErr: errors.New("no valid metric exporter"), }, { name: "periodic/console-exporter", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Exporter: PushMetricExporter{ Console: Console{}, }, }, }, wantReader: sdkmetric.NewPeriodicReader(consoleExporter), }, { name: "periodic/console-exporter-with-extra-options", reader: MetricReader{ Periodic: &PeriodicMetricReader{ Interval: ptr(30_000), Timeout: ptr(5_000), Exporter: PushMetricExporter{ Console: Console{}, }, }, }, wantReader: sdkmetric.NewPeriodicReader( consoleExporter, sdkmetric.WithInterval(30_000*time.Millisecond), sdkmetric.WithTimeout(5_000*time.Millisecond), ), }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { got, err := metricReader(context.Background(), tt.reader) require.Equal(t, tt.wantErr, err) if tt.wantReader == nil { require.Nil(t, got) } else { require.Equal(t, reflect.TypeOf(tt.wantReader), reflect.TypeOf(got)) var fieldName string switch reflect.TypeOf(tt.wantReader).String() { case "*metric.PeriodicReader": fieldName = "exporter" case "config.readerWithServer": fieldName = "Reader" default: fieldName = "e" } wantExporterType := reflect.Indirect(reflect.ValueOf(tt.wantReader)).FieldByName(fieldName).Elem().Type() gotExporterType := reflect.Indirect(reflect.ValueOf(got)).FieldByName(fieldName).Elem().Type() require.Equal(t, wantExporterType.String(), gotExporterType.String()) require.NoError(t, got.Shutdown(context.Background())) } }) } } func TestView(t *testing.T) { testCases := []struct { name string view View args any wantErr string matchInstrument *sdkmetric.Instrument wantStream sdkmetric.Stream wantResult bool }{ { name: "no selector", wantErr: "view: no selector provided", }, { name: "selector/invalid_type", view: View{ Selector: &ViewSelector{ InstrumentType: (*ViewSelectorInstrumentType)(ptr("invalid_type")), }, }, wantErr: "view_selector: instrument_type: invalid value", }, { name: "selector/invalid_type", view: View{ Selector: &ViewSelector{}, }, wantErr: "view_selector: empty selector not supporter", }, { name: "all selectors match", view: View{ Selector: &ViewSelector{ InstrumentName: ptr("test_name"), InstrumentType: (*ViewSelectorInstrumentType)(ptr("counter")), Unit: ptr("test_unit"), MeterName: ptr("test_meter_name"), MeterVersion: ptr("test_meter_version"), MeterSchemaUrl: ptr("test_schema_url"), }, }, matchInstrument: &sdkmetric.Instrument{ Name: "test_name", Unit: "test_unit", Kind: sdkmetric.InstrumentKindCounter, Scope: instrumentation.Scope{ Name: "test_meter_name", Version: "test_meter_version", SchemaURL: "test_schema_url", }, }, wantStream: sdkmetric.Stream{Name: "test_name", Unit: "test_unit"}, wantResult: true, }, { name: "all selectors no match name", view: View{ Selector: &ViewSelector{ InstrumentName: ptr("test_name"), InstrumentType: (*ViewSelectorInstrumentType)(ptr("counter")), Unit: ptr("test_unit"), MeterName: ptr("test_meter_name"), MeterVersion: ptr("test_meter_version"), MeterSchemaUrl: ptr("test_schema_url"), }, }, matchInstrument: &sdkmetric.Instrument{ Name: "not_match", Unit: "test_unit", Kind: sdkmetric.InstrumentKindCounter, Scope: instrumentation.Scope{ Name: "test_meter_name", Version: "test_meter_version", SchemaURL: "test_schema_url", }, }, wantStream: sdkmetric.Stream{}, wantResult: false, }, { name: "all selectors no match unit", view: View{ Selector: &ViewSelector{ InstrumentName: ptr("test_name"), InstrumentType: (*ViewSelectorInstrumentType)(ptr("counter")), Unit: ptr("test_unit"), MeterName: ptr("test_meter_name"), MeterVersion: ptr("test_meter_version"), MeterSchemaUrl: ptr("test_schema_url"), }, }, matchInstrument: &sdkmetric.Instrument{ Name: "test_name", Unit: "not_match", Kind: sdkmetric.InstrumentKindCounter, Scope: instrumentation.Scope{ Name: "test_meter_name", Version: "test_meter_version", SchemaURL: "test_schema_url", }, }, wantStream: sdkmetric.Stream{}, wantResult: false, }, { name: "all selectors no match kind", view: View{ Selector: &ViewSelector{ InstrumentName: ptr("test_name"), InstrumentType: (*ViewSelectorInstrumentType)(ptr("histogram")), Unit: ptr("test_unit"), MeterName: ptr("test_meter_name"), MeterVersion: ptr("test_meter_version"), MeterSchemaUrl: ptr("test_schema_url"), }, }, matchInstrument: &sdkmetric.Instrument{ Name: "test_name", Unit: "test_unit", Kind: sdkmetric.InstrumentKindCounter, Scope: instrumentation.Scope{ Name: "test_meter_name", Version: "test_meter_version", SchemaURL: "test_schema_url", }, }, wantStream: sdkmetric.Stream{}, wantResult: false, }, { name: "all selectors no match meter name", view: View{ Selector: &ViewSelector{ InstrumentName: ptr("test_name"), InstrumentType: (*ViewSelectorInstrumentType)(ptr("counter")), Unit: ptr("test_unit"), MeterName: ptr("test_meter_name"), MeterVersion: ptr("test_meter_version"), MeterSchemaUrl: ptr("test_schema_url"), }, }, matchInstrument: &sdkmetric.Instrument{ Name: "test_name", Unit: "test_unit", Kind: sdkmetric.InstrumentKindCounter, Scope: instrumentation.Scope{ Name: "not_match", Version: "test_meter_version", SchemaURL: "test_schema_url", }, }, wantStream: sdkmetric.Stream{}, wantResult: false, }, { name: "all selectors no match meter version", view: View{ Selector: &ViewSelector{ InstrumentName: ptr("test_name"), InstrumentType: (*ViewSelectorInstrumentType)(ptr("counter")), Unit: ptr("test_unit"), MeterName: ptr("test_meter_name"), MeterVersion: ptr("test_meter_version"), MeterSchemaUrl: ptr("test_schema_url"), }, }, matchInstrument: &sdkmetric.Instrument{ Name: "test_name", Unit: "test_unit", Kind: sdkmetric.InstrumentKindCounter, Scope: instrumentation.Scope{ Name: "test_meter_name", Version: "not_match", SchemaURL: "test_schema_url", }, }, wantStream: sdkmetric.Stream{}, wantResult: false, }, { name: "all selectors no match meter schema url", view: View{ Selector: &ViewSelector{ InstrumentName: ptr("test_name"), InstrumentType: (*ViewSelectorInstrumentType)(ptr("counter")), Unit: ptr("test_unit"), MeterName: ptr("test_meter_name"), MeterVersion: ptr("test_meter_version"), MeterSchemaUrl: ptr("test_schema_url"), }, }, matchInstrument: &sdkmetric.Instrument{ Name: "test_name", Unit: "test_unit", Kind: sdkmetric.InstrumentKindCounter, Scope: instrumentation.Scope{ Name: "test_meter_name", Version: "test_meter_version", SchemaURL: "not_match", }, }, wantStream: sdkmetric.Stream{}, wantResult: false, }, { name: "with stream", view: View{ Selector: &ViewSelector{ InstrumentName: ptr("test_name"), Unit: ptr("test_unit"), }, Stream: &ViewStream{ Name: ptr("new_name"), Description: ptr("new_description"), AttributeKeys: ptr(IncludeExclude{Included: []string{"foo", "bar"}}), Aggregation: &ViewStreamAggregation{Sum: make(ViewStreamAggregationSum)}, }, }, matchInstrument: &sdkmetric.Instrument{ Name: "test_name", Description: "test_description", Unit: "test_unit", }, wantStream: sdkmetric.Stream{ Name: "new_name", Description: "new_description", Unit: "test_unit", Aggregation: sdkmetric.AggregationSum{}, }, wantResult: true, }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { got, err := view(tt.view) if tt.wantErr != "" { require.EqualError(t, err, tt.wantErr) require.Nil(t, got) } else { require.NoError(t, err) gotStream, gotResult := got(*tt.matchInstrument) // Remove filter, since it cannot be compared gotStream.AttributeFilter = nil require.Equal(t, tt.wantStream, gotStream) require.Equal(t, tt.wantResult, gotResult) } }) } } func TestInstrumentType(t *testing.T) { testCases := []struct { name string instType *ViewSelectorInstrumentType wantErr error wantKind sdkmetric.InstrumentKind }{ { name: "nil", wantKind: sdkmetric.InstrumentKind(0), }, { name: "counter", instType: (*ViewSelectorInstrumentType)(ptr("counter")), wantKind: sdkmetric.InstrumentKindCounter, }, { name: "up_down_counter", instType: (*ViewSelectorInstrumentType)(ptr("up_down_counter")), wantKind: sdkmetric.InstrumentKindUpDownCounter, }, { name: "histogram", instType: (*ViewSelectorInstrumentType)(ptr("histogram")), wantKind: sdkmetric.InstrumentKindHistogram, }, { name: "observable_counter", instType: (*ViewSelectorInstrumentType)(ptr("observable_counter")), wantKind: sdkmetric.InstrumentKindObservableCounter, }, { name: "observable_up_down_counter", instType: (*ViewSelectorInstrumentType)(ptr("observable_up_down_counter")), wantKind: sdkmetric.InstrumentKindObservableUpDownCounter, }, { name: "observable_gauge", instType: (*ViewSelectorInstrumentType)(ptr("observable_gauge")), wantKind: sdkmetric.InstrumentKindObservableGauge, }, { name: "invalid", instType: (*ViewSelectorInstrumentType)(ptr("invalid")), wantErr: errors.New("instrument_type: invalid value"), }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { got, err := instrumentKind(tt.instType) if tt.wantErr != nil { require.Equal(t, tt.wantErr, err) require.Zero(t, got) } else { require.NoError(t, err) require.Equal(t, tt.wantKind, got) } }) } } func TestAggregation(t *testing.T) { testCases := []struct { name string aggregation *ViewStreamAggregation wantAggregation sdkmetric.Aggregation }{ { name: "nil", wantAggregation: nil, }, { name: "empty", aggregation: &ViewStreamAggregation{}, wantAggregation: nil, }, { name: "Base2ExponentialBucketHistogram empty", aggregation: &ViewStreamAggregation{ Base2ExponentialBucketHistogram: &ViewStreamAggregationBase2ExponentialBucketHistogram{}, }, wantAggregation: sdkmetric.AggregationBase2ExponentialHistogram{ MaxSize: 0, MaxScale: 0, NoMinMax: true, }, }, { name: "Base2ExponentialBucketHistogram", aggregation: &ViewStreamAggregation{ Base2ExponentialBucketHistogram: &ViewStreamAggregationBase2ExponentialBucketHistogram{ MaxSize: ptr(2), MaxScale: ptr(3), RecordMinMax: ptr(true), }, }, wantAggregation: sdkmetric.AggregationBase2ExponentialHistogram{ MaxSize: 2, MaxScale: 3, NoMinMax: false, }, }, { name: "Default", aggregation: &ViewStreamAggregation{ Default: make(ViewStreamAggregationDefault), }, wantAggregation: nil, }, { name: "Drop", aggregation: &ViewStreamAggregation{ Drop: make(ViewStreamAggregationDrop), }, wantAggregation: sdkmetric.AggregationDrop{}, }, { name: "ExplicitBucketHistogram empty", aggregation: &ViewStreamAggregation{ ExplicitBucketHistogram: &ViewStreamAggregationExplicitBucketHistogram{}, }, wantAggregation: sdkmetric.AggregationExplicitBucketHistogram{ Boundaries: nil, NoMinMax: true, }, }, { name: "ExplicitBucketHistogram", aggregation: &ViewStreamAggregation{ ExplicitBucketHistogram: &ViewStreamAggregationExplicitBucketHistogram{ Boundaries: []float64{1, 2, 3}, RecordMinMax: ptr(true), }, }, wantAggregation: sdkmetric.AggregationExplicitBucketHistogram{ Boundaries: []float64{1, 2, 3}, NoMinMax: false, }, }, { name: "LastValue", aggregation: &ViewStreamAggregation{ LastValue: make(ViewStreamAggregationLastValue), }, wantAggregation: sdkmetric.AggregationLastValue{}, }, { name: "Sum", aggregation: &ViewStreamAggregation{ Sum: make(ViewStreamAggregationSum), }, wantAggregation: sdkmetric.AggregationSum{}, }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { got := aggregation(tt.aggregation) require.Equal(t, tt.wantAggregation, got) }) } } func TestNewIncludeExcludeFilter(t *testing.T) { testCases := []struct { name string attributeKeys *IncludeExclude wantPass []string wantFail []string }{ { name: "empty", attributeKeys: nil, wantPass: []string{"foo", "bar"}, wantFail: nil, }, { name: "filter-with-include", attributeKeys: ptr(IncludeExclude{ Included: []string{"foo"}, }), wantPass: []string{"foo"}, wantFail: []string{"bar"}, }, { name: "filter-with-exclude", attributeKeys: ptr(IncludeExclude{ Excluded: []string{"foo"}, }), wantPass: []string{"bar"}, wantFail: []string{"foo"}, }, { name: "filter-with-include-and-exclude", attributeKeys: ptr(IncludeExclude{ Included: []string{"bar"}, Excluded: []string{"foo"}, }), wantPass: []string{"bar"}, wantFail: []string{"foo"}, }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { got, err := newIncludeExcludeFilter(tt.attributeKeys) require.NoError(t, err) for _, pass := range tt.wantPass { require.True(t, got(attribute.KeyValue{Key: attribute.Key(pass), Value: attribute.StringValue("")})) } for _, fail := range tt.wantFail { require.False(t, got(attribute.KeyValue{Key: attribute.Key(fail), Value: attribute.StringValue("")})) } }) } } func TestNewIncludeExcludeFilterError(t *testing.T) { _, err := newIncludeExcludeFilter(ptr(IncludeExclude{ Included: []string{"foo"}, Excluded: []string{"foo"}, })) require.Equal(t, fmt.Errorf("attribute cannot be in both include and exclude list: foo"), err) } open-telemetry-opentelemetry-go-contrib-e5abccb/config/resource.go000066400000000000000000000027171470323427300257030ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package config // import "go.opentelemetry.io/contrib/config" import ( "fmt" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" ) func keyVal(k string, v any) attribute.KeyValue { switch val := v.(type) { case bool: return attribute.Bool(k, val) case int64: return attribute.Int64(k, val) case uint64: return attribute.String(k, fmt.Sprintf("%d", val)) case float64: return attribute.Float64(k, val) case int8: return attribute.Int64(k, int64(val)) case uint8: return attribute.Int64(k, int64(val)) case int16: return attribute.Int64(k, int64(val)) case uint16: return attribute.Int64(k, int64(val)) case int32: return attribute.Int64(k, int64(val)) case uint32: return attribute.Int64(k, int64(val)) case float32: return attribute.Float64(k, float64(val)) case int: return attribute.Int(k, val) case uint: return attribute.String(k, fmt.Sprintf("%d", val)) case string: return attribute.String(k, val) default: return attribute.String(k, fmt.Sprint(v)) } } func newResource(res *Resource) (*resource.Resource, error) { if res == nil || res.Attributes == nil { return resource.Default(), nil } var attrs []attribute.KeyValue for _, v := range res.Attributes { attrs = append(attrs, keyVal(v.Name, v.Value)) } return resource.Merge(resource.Default(), resource.NewWithAttributes(*res.SchemaUrl, attrs..., )) } open-telemetry-opentelemetry-go-contrib-e5abccb/config/resource_test.go000066400000000000000000000070441470323427300267400ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package config // import "go.opentelemetry.io/contrib/config" import ( "fmt" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.26.0" ) type mockType struct{} func TestNewResource(t *testing.T) { res, err := resource.Merge(resource.Default(), resource.NewWithAttributes(semconv.SchemaURL, semconv.ServiceName("service-a"), )) other := mockType{} require.NoError(t, err) resWithAttrs, err := resource.Merge(resource.Default(), resource.NewWithAttributes(semconv.SchemaURL, semconv.ServiceName("service-a"), attribute.Bool("attr-bool", true), attribute.String("attr-uint64", fmt.Sprintf("%d", 164)), attribute.Int64("attr-int64", int64(-164)), attribute.Float64("attr-float64", float64(64.0)), attribute.Int64("attr-int8", int64(-18)), attribute.Int64("attr-uint8", int64(18)), attribute.Int64("attr-int16", int64(-116)), attribute.Int64("attr-uint16", int64(116)), attribute.Int64("attr-int32", int64(-132)), attribute.Int64("attr-uint32", int64(132)), attribute.Float64("attr-float32", float64(32.0)), attribute.Int64("attr-int", int64(-1)), attribute.String("attr-uint", fmt.Sprintf("%d", 1)), attribute.String("attr-string", "string-val"), attribute.String("attr-default", fmt.Sprintf("%v", other)), )) require.NoError(t, err) tests := []struct { name string config *Resource wantResource *resource.Resource wantErr error }{ { name: "no-resource-configuration", wantResource: resource.Default(), }, { name: "resource-no-attributes", config: &Resource{}, wantResource: resource.Default(), }, { name: "resource-with-attributes-invalid-schema", config: &Resource{ SchemaUrl: ptr("https://opentelemetry.io/invalid-schema"), Attributes: []AttributeNameValue{ {Name: "service.name", Value: "service-a"}, }, }, wantResource: resource.NewSchemaless(res.Attributes()...), wantErr: resource.ErrSchemaURLConflict, }, { name: "resource-with-attributes-and-schema", config: &Resource{ Attributes: []AttributeNameValue{ {Name: "service.name", Value: "service-a"}, }, SchemaUrl: ptr(semconv.SchemaURL), }, wantResource: res, }, { name: "resource-with-additional-attributes-and-schema", config: &Resource{ Attributes: []AttributeNameValue{ {Name: "service.name", Value: "service-a"}, {Name: "attr-bool", Value: true}, {Name: "attr-int64", Value: int64(-164)}, {Name: "attr-uint64", Value: uint64(164)}, {Name: "attr-float64", Value: float64(64.0)}, {Name: "attr-int8", Value: int8(-18)}, {Name: "attr-uint8", Value: uint8(18)}, {Name: "attr-int16", Value: int16(-116)}, {Name: "attr-uint16", Value: uint16(116)}, {Name: "attr-int32", Value: int32(-132)}, {Name: "attr-uint32", Value: uint32(132)}, {Name: "attr-float32", Value: float32(32.0)}, {Name: "attr-int", Value: int(-1)}, {Name: "attr-uint", Value: uint(1)}, {Name: "attr-string", Value: "string-val"}, {Name: "attr-default", Value: other}, }, SchemaUrl: ptr(semconv.SchemaURL), }, wantResource: resWithAttrs, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := newResource(tt.config) assert.ErrorIs(t, err, tt.wantErr) assert.Equal(t, tt.wantResource, got) }) } } open-telemetry-opentelemetry-go-contrib-e5abccb/config/testdata/000077500000000000000000000000001470323427300253275ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/config/testdata/invalid_bool.json000066400000000000000000000000571470323427300306650ustar00rootroot00000000000000{"file_format": "yaml", "disabled": "notabool"}open-telemetry-opentelemetry-go-contrib-e5abccb/config/testdata/invalid_bool.yaml000066400000000000000000000000441470323427300306520ustar00rootroot00000000000000file_format: yaml disabled: notaboolopen-telemetry-opentelemetry-go-contrib-e5abccb/config/testdata/v0.2.json000066400000000000000000000166541470323427300267230ustar00rootroot00000000000000{ "file_format": "0.2", "disabled": false, "attribute_limits": { "attribute_value_length_limit": 4096, "attribute_count_limit": 128 }, "logger_provider": { "processors": [ { "batch": { "schedule_delay": 5000, "export_timeout": 30000, "max_queue_size": 2048, "max_export_batch_size": 512, "exporter": { "otlp": { "protocol": "http/protobuf", "endpoint": "http://localhost:4318", "certificate": "/app/cert.pem", "client_key": "/app/cert.pem", "client_certificate": "/app/cert.pem", "headers": { "api-key": "1234" }, "compression": "gzip", "timeout": 10000, "insecure": false } } } }, { "simple": { "exporter": { "console": {} } } } ], "limits": { "attribute_value_length_limit": 4096, "attribute_count_limit": 128 } }, "meter_provider": { "readers": [ { "pull": { "exporter": { "prometheus": { "host": "localhost", "port": 9464, "without_units": false, "without_type_suffix": false, "without_scope_info": false, "with_resource_constant_labels": { "included": [ "service*" ], "excluded": [ "service.attr1" ] } } } } }, { "periodic": { "interval": 5000, "timeout": 30000, "exporter": { "otlp": { "protocol": "http/protobuf", "endpoint": "http://localhost:4318", "certificate": "/app/cert.pem", "client_key": "/app/cert.pem", "client_certificate": "/app/cert.pem", "headers": { "api-key": "1234" }, "compression": "gzip", "timeout": 10000, "insecure": false, "temporality_preference": "delta", "default_histogram_aggregation": "base2_exponential_bucket_histogram" } } } }, { "periodic": { "exporter": { "console": {} } } } ], "views": [ { "selector": { "instrument_name": "my-instrument", "instrument_type": "histogram", "unit": "ms", "meter_name": "my-meter", "meter_version": "1.0.0", "meter_schema_url": "https://opentelemetry.io/schemas/1.16.0" }, "stream": { "name": "new_instrument_name", "description": "new_description", "aggregation": { "explicit_bucket_histogram": { "boundaries": [ 0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000 ], "record_min_max": true } }, "attribute_keys": [ "key1", "key2" ] } } ] }, "propagator": { "composite": [ "tracecontext", "baggage", "b3", "b3multi", "jaeger", "xray", "ottrace" ] }, "tracer_provider": { "processors": [ { "batch": { "schedule_delay": 5000, "export_timeout": 30000, "max_queue_size": 2048, "max_export_batch_size": 512, "exporter": { "otlp": { "protocol": "http/protobuf", "endpoint": "http://localhost:4318", "certificate": "/app/cert.pem", "client_key": "/app/cert.pem", "client_certificate": "/app/cert.pem", "headers": { "api-key": "1234" }, "compression": "gzip", "timeout": 10000, "insecure": false } } } }, { "batch": { "exporter": { "zipkin": { "endpoint": "http://localhost:9411/api/v2/spans", "timeout": 10000 } } } }, { "simple": { "exporter": { "console": {} } } } ], "limits": { "attribute_value_length_limit": 4096, "attribute_count_limit": 128, "event_count_limit": 128, "link_count_limit": 128, "event_attribute_count_limit": 128, "link_attribute_count_limit": 128 }, "sampler": { "parent_based": { "root": { "trace_id_ratio_based": { "ratio": 0.0001 } }, "remote_parent_sampled": { "always_on": {} }, "remote_parent_not_sampled": { "always_off": {} }, "local_parent_sampled": { "always_on": {} }, "local_parent_not_sampled": { "always_off": {} } } } }, "resource": { "attributes": { "service.name": "unknown_service" }, "detectors": { "attributes": { "included": [ "process.*" ], "excluded": [ "process.command_args" ] } }, "schema_url": "https://opentelemetry.io/schemas/1.16.0" } }open-telemetry-opentelemetry-go-contrib-e5abccb/config/testdata/v0.2.yaml000066400000000000000000000432611470323427300267060ustar00rootroot00000000000000# kitchen-sink.yaml demonstrates all configurable surface area, including explanatory comments. # # It DOES NOT represent expected real world configuration, as it makes strange configuration # choices in an effort to exercise the full surface area. # # Configuration values are set to their defaults when default values are defined. # The file format version file_format: "0.2" # Configure if the SDK is disabled or not. This is not required to be provided # to ensure the SDK isn't disabled, the default value when this is not provided # is for the SDK to be enabled. # # Environment variable: OTEL_SDK_DISABLED disabled: false # Configure general attribute limits. See also tracer_provider.limits, logger_provider.limits. attribute_limits: # Configure max attribute value size. # # Environment variable: OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT attribute_value_length_limit: 4096 # Configure max attribute count. # # Environment variable: OTEL_ATTRIBUTE_COUNT_LIMIT attribute_count_limit: 128 # Configure logger provider. logger_provider: # Configure log record processors. processors: # Configure a batch log record processor. - batch: # Configure delay interval (in milliseconds) between two consecutive exports. # # Environment variable: OTEL_BLRP_SCHEDULE_DELAY schedule_delay: 5000 # Configure maximum allowed time (in milliseconds) to export data. # # Environment variable: OTEL_BLRP_EXPORT_TIMEOUT export_timeout: 30000 # Configure maximum queue size. # # Environment variable: OTEL_BLRP_MAX_QUEUE_SIZE max_queue_size: 2048 # Configure maximum batch size. # # Environment variable: OTEL_BLRP_MAX_EXPORT_BATCH_SIZE max_export_batch_size: 512 # Configure exporter. # # Environment variable: OTEL_LOGS_EXPORTER exporter: # Configure exporter to be OTLP. otlp: # Configure protocol. # # Environment variable: OTEL_EXPORTER_OTLP_PROTOCOL, OTEL_EXPORTER_OTLP_LOGS_PROTOCOL protocol: http/protobuf # Configure endpoint. # # Environment variable: OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_LOGS_ENDPOINT endpoint: http://localhost:4318 # Configure certificate. # # Environment variable: OTEL_EXPORTER_OTLP_CERTIFICATE, OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE certificate: /app/cert.pem # Configure mTLS private client key. # # Environment variable: OTEL_EXPORTER_OTLP_CLIENT_KEY, OTEL_EXPORTER_OTLP_LOGS_CLIENT_KEY client_key: /app/cert.pem # Configure mTLS client certificate. # # Environment variable: OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE, OTEL_EXPORTER_OTLP_LOGS_CLIENT_CERTIFICATE client_certificate: /app/cert.pem # Configure headers. # # Environment variable: OTEL_EXPORTER_OTLP_HEADERS, OTEL_EXPORTER_OTLP_LOGS_HEADERS headers: api-key: "1234" # Configure compression. # # Environment variable: OTEL_EXPORTER_OTLP_COMPRESSION, OTEL_EXPORTER_OTLP_LOGS_COMPRESSION compression: gzip # Configure max time (in milliseconds) to wait for each export. # # Environment variable: OTEL_EXPORTER_OTLP_TIMEOUT, OTEL_EXPORTER_OTLP_LOGS_TIMEOUT timeout: 10000 # Configure client transport security for the exporter's connection. # # Environment variable: OTEL_EXPORTER_OTLP_INSECURE, OTEL_EXPORTER_OTLP_LOGS_INSECURE insecure: false # Configure a simple span processor. - simple: # Configure exporter. exporter: # Configure exporter to be console. console: {} # Configure log record limits. See also attribute_limits. limits: # Configure max log record attribute value size. Overrides attribute_limits.attribute_value_length_limit. # # Environment variable: OTEL_LOGRECORD_ATTRIBUTE_VALUE_LENGTH_LIMIT attribute_value_length_limit: 4096 # Configure max log record attribute count. Overrides attribute_limits.attribute_count_limit. # # Environment variable: OTEL_LOGRECORD_ATTRIBUTE_COUNT_LIMIT attribute_count_limit: 128 # Configure meter provider. meter_provider: # Configure metric readers. readers: # Configure a pull-based metric reader. - pull: # Configure exporter. # # Environment variable: OTEL_METRICS_EXPORTER exporter: # Configure exporter to be prometheus. prometheus: # Configure host. # # Environment variable: OTEL_EXPORTER_PROMETHEUS_HOST host: localhost # Configure port. # # Environment variable: OTEL_EXPORTER_PROMETHEUS_PORT port: 9464 # Configure Prometheus Exporter to produce metrics without a unit suffix or UNIT metadata. without_units: false # Configure Prometheus Exporter to produce metrics without a type suffix. without_type_suffix: false # Configure Prometheus Exporter to produce metrics without a scope info metric. without_scope_info: false # Configure Prometheus Exporter to add resource attributes as metrics attributes. with_resource_constant_labels: # Configure resource attributes to be included, in this example attributes starting with service. included: - "service*" # Configure resource attributes to be excluded, in this example attribute service.attr1. excluded: - "service.attr1" # Configure a periodic metric reader. - periodic: # Configure delay interval (in milliseconds) between start of two consecutive exports. # # Environment variable: OTEL_METRIC_EXPORT_INTERVAL interval: 5000 # Configure maximum allowed time (in milliseconds) to export data. # # Environment variable: OTEL_METRIC_EXPORT_TIMEOUT timeout: 30000 # Configure exporter. # # Environment variable: OTEL_METRICS_EXPORTER exporter: # Configure exporter to be OTLP. otlp: # Configure protocol. # # Environment variable: OTEL_EXPORTER_OTLP_PROTOCOL, OTEL_EXPORTER_OTLP_METRICS_PROTOCOL protocol: http/protobuf # Configure endpoint. # # Environment variable: OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_METRICS_ENDPOINT endpoint: http://localhost:4318 # Configure certificate. # # Environment variable: OTEL_EXPORTER_OTLP_CERTIFICATE, OTEL_EXPORTER_OTLP_METRICS_CERTIFICATE certificate: /app/cert.pem # Configure mTLS private client key. # # Environment variable: OTEL_EXPORTER_OTLP_CLIENT_KEY, OTEL_EXPORTER_OTLP_METRICS_CLIENT_KEY client_key: /app/cert.pem # Configure mTLS client certificate. # # Environment variable: OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE, OTEL_EXPORTER_OTLP_METRICS_CLIENT_CERTIFICATE client_certificate: /app/cert.pem # Configure headers. # # Environment variable: OTEL_EXPORTER_OTLP_HEADERS, OTEL_EXPORTER_OTLP_METRICS_HEADERS headers: api-key: !!str 1234 # Configure compression. # # Environment variable: OTEL_EXPORTER_OTLP_COMPRESSION, OTEL_EXPORTER_OTLP_METRICS_COMPRESSION compression: gzip # Configure max time (in milliseconds) to wait for each export. # # Environment variable: OTEL_EXPORTER_OTLP_TIMEOUT, OTEL_EXPORTER_OTLP_METRICS_TIMEOUT timeout: 10000 # Configure client transport security for the exporter's connection. # # Environment variable: OTEL_EXPORTER_OTLP_INSECURE, OTEL_EXPORTER_OTLP_METRICS_INSECURE insecure: false # Configure temporality preference. # # Environment variable: OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE temporality_preference: delta # Configure default histogram aggregation. # # Environment variable: OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION default_histogram_aggregation: base2_exponential_bucket_histogram # Configure a periodic metric reader. - periodic: # Configure exporter. exporter: # Configure exporter to be console. console: {} # Configure views. Each view has a selector which determines the instrument(s) it applies to, and a configuration for the resulting stream(s). views: # Configure a view. - selector: # Configure instrument name selection criteria. instrument_name: my-instrument # Configure instrument type selection criteria. instrument_type: histogram # Configure the instrument unit selection criteria. unit: ms # Configure meter name selection criteria. meter_name: my-meter # Configure meter version selection criteria. meter_version: 1.0.0 # Configure meter schema url selection criteria. meter_schema_url: https://opentelemetry.io/schemas/1.16.0 # Configure stream. stream: # Configure metric name of the resulting stream(s). name: new_instrument_name # Configure metric description of the resulting stream(s). description: new_description # Configure aggregation of the resulting stream(s). Known values include: default, drop, explicit_bucket_histogram, base2_exponential_bucket_histogram, last_value, sum. aggregation: # Configure aggregation to be explicit_bucket_histogram. explicit_bucket_histogram: # Configure bucket boundaries. boundaries: [ 0.0, 5.0, 10.0, 25.0, 50.0, 75.0, 100.0, 250.0, 500.0, 750.0, 1000.0, 2500.0, 5000.0, 7500.0, 10000.0 ] # Configure record min and max. record_min_max: true # Configure attribute keys retained in the resulting stream(s). attribute_keys: - key1 - key2 # Configure text map context propagators. # # Environment variable: OTEL_PROPAGATORS propagator: composite: [tracecontext, baggage, b3, b3multi, jaeger, xray, ottrace] # Configure tracer provider. tracer_provider: # Configure span processors. processors: # Configure a batch span processor. - batch: # Configure delay interval (in milliseconds) between two consecutive exports. # # Environment variable: OTEL_BSP_SCHEDULE_DELAY schedule_delay: 5000 # Configure maximum allowed time (in milliseconds) to export data. # # Environment variable: OTEL_BSP_EXPORT_TIMEOUT export_timeout: 30000 # Configure maximum queue size. # # Environment variable: OTEL_BSP_MAX_QUEUE_SIZE max_queue_size: 2048 # Configure maximum batch size. # # Environment variable: OTEL_BSP_MAX_EXPORT_BATCH_SIZE max_export_batch_size: 512 # Configure exporter. # # Environment variable: OTEL_TRACES_EXPORTER exporter: # Configure exporter to be OTLP. otlp: # Configure protocol. # # Environment variable: OTEL_EXPORTER_OTLP_PROTOCOL, OTEL_EXPORTER_OTLP_TRACES_PROTOCOL protocol: http/protobuf # Configure endpoint. # # Environment variable: OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_TRACES_ENDPOINT endpoint: http://localhost:4318 # Configure certificate. # # Environment variable: OTEL_EXPORTER_OTLP_CERTIFICATE, OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE certificate: /app/cert.pem # Configure mTLS private client key. # # Environment variable: OTEL_EXPORTER_OTLP_CLIENT_KEY, OTEL_EXPORTER_OTLP_TRACES_CLIENT_KEY client_key: /app/cert.pem # Configure mTLS client certificate. # # Environment variable: OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE, OTEL_EXPORTER_OTLP_TRACES_CLIENT_CERTIFICATE client_certificate: /app/cert.pem # Configure headers. # # Environment variable: OTEL_EXPORTER_OTLP_HEADERS, OTEL_EXPORTER_OTLP_TRACES_HEADERS headers: api-key: !!str 1234 # Configure compression. # # Environment variable: OTEL_EXPORTER_OTLP_COMPRESSION, OTEL_EXPORTER_OTLP_TRACES_COMPRESSION compression: gzip # Configure max time (in milliseconds) to wait for each export. # # Environment variable: OTEL_EXPORTER_OTLP_TIMEOUT, OTEL_EXPORTER_OTLP_TRACES_TIMEOUT timeout: 10000 # Configure client transport security for the exporter's connection. # # Environment variable: OTEL_EXPORTER_OTLP_INSECURE, OTEL_EXPORTER_OTLP_TRACES_INSECURE insecure: false # Configure a batch span processor. - batch: # Configure exporter. # # Environment variable: OTEL_TRACES_EXPORTER exporter: # Configure exporter to be zipkin. zipkin: # Configure endpoint. # # Environment variable: OTEL_EXPORTER_ZIPKIN_ENDPOINT endpoint: http://localhost:9411/api/v2/spans # Configure max time (in milliseconds) to wait for each export. # # Environment variable: OTEL_EXPORTER_ZIPKIN_TIMEOUT timeout: 10000 # Configure a simple span processor. - simple: # Configure exporter. exporter: # Configure exporter to be console. console: {} # Configure span limits. See also attribute_limits. limits: # Configure max span attribute value size. Overrides attribute_limits.attribute_value_length_limit. # # Environment variable: OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT attribute_value_length_limit: 4096 # Configure max span attribute count. Overrides attribute_limits.attribute_count_limit. # # Environment variable: OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT attribute_count_limit: 128 # Configure max span event count. # # Environment variable: OTEL_SPAN_EVENT_COUNT_LIMIT event_count_limit: 128 # Configure max span link count. # # Environment variable: OTEL_SPAN_LINK_COUNT_LIMIT link_count_limit: 128 # Configure max attributes per span event. # # Environment variable: OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT event_attribute_count_limit: 128 # Configure max attributes per span link. # # Environment variable: OTEL_LINK_ATTRIBUTE_COUNT_LIMIT link_attribute_count_limit: 128 # Configure the sampler. sampler: # Configure sampler to be parent_based. Known values include: always_off, always_on, jaeger_remote, parent_based, trace_id_ratio_based. # # Environment variable: OTEL_TRACES_SAMPLER=parentbased_* parent_based: # Configure root sampler. # # Environment variable: OTEL_TRACES_SAMPLER=parentbased_traceidratio root: # Configure sampler to be trace_id_ratio_based. trace_id_ratio_based: # Configure trace_id_ratio. # # Environment variable: OTEL_TRACES_SAMPLER_ARG=traceidratio=0.0001 ratio: 0.0001 # Configure remote_parent_sampled sampler. remote_parent_sampled: # Configure sampler to be always_on. always_on: {} # Configure remote_parent_not_sampled sampler. remote_parent_not_sampled: # Configure sampler to be always_off. always_off: {} # Configure local_parent_sampled sampler. local_parent_sampled: # Configure sampler to be always_on. always_on: {} # Configure local_parent_not_sampled sampler. local_parent_not_sampled: # Configure sampler to be always_off. always_off: {} # Configure resource for all signals. resource: # Configure resource attributes. # # Environment variable: OTEL_RESOURCE_ATTRIBUTES attributes: # Configure `service.name` resource attribute # # Environment variable: OTEL_SERVICE_NAME service.name: !!str "unknown_service" # Configure resource detectors. detectors: # Configure attributes provided by resource detectors. attributes: # Configure list of attribute key patterns to include from resource detectors. If not set, all attributes are included. # # Attribute keys from resource detectors are evaluated to match as follows: # * If the value of the attribute key exactly matches. # * If the value of the attribute key matches the wildcard pattern, where '?' matches any single character and '*' matches any number of characters including none. included: - process.* # Configure list of attribute key patterns to exclude from resource detectors. Applies after .resource.detectors.attributes.included (i.e. excluded has higher priority than included). # # Attribute keys from resource detectors are evaluated to match as follows: # * If the value of the attribute key exactly matches. # * If the value of the attribute key matches the wildcard pattern, where '?' matches any single character and '*' matches any number of characters including none. excluded: - process.command_args # Configure the resource schema URL. schema_url: https://opentelemetry.io/schemas/1.16.0 open-telemetry-opentelemetry-go-contrib-e5abccb/config/testdata/v0.3.json000066400000000000000000000311401470323427300267070ustar00rootroot00000000000000{ "file_format": "0.3", "disabled": false, "attribute_limits": { "attribute_value_length_limit": 4096, "attribute_count_limit": 128 }, "logger_provider": { "processors": [ { "batch": { "schedule_delay": 5000, "export_timeout": 30000, "max_queue_size": 2048, "max_export_batch_size": 512, "exporter": { "otlp": { "protocol": "http/protobuf", "endpoint": "http://localhost:4318/v1/logs", "certificate": "/app/cert.pem", "client_key": "/app/cert.pem", "client_certificate": "/app/cert.pem", "headers": [ { "name": "api-key", "value": "1234" } ], "headers_list": "api-key=1234", "compression": "gzip", "timeout": 10000, "insecure": false } } } }, { "simple": { "exporter": { "console": {} } } } ], "limits": { "attribute_value_length_limit": 4096, "attribute_count_limit": 128 } }, "meter_provider": { "readers": [ { "pull": { "exporter": { "prometheus": { "host": "localhost", "port": 9464, "without_units": false, "without_type_suffix": false, "without_scope_info": false, "with_resource_constant_labels": { "included": [ "service*" ], "excluded": [ "service.attr1" ] } } } }, "producers": [ { "opencensus": {} } ] }, { "periodic": { "interval": 5000, "timeout": 30000, "exporter": { "otlp": { "protocol": "http/protobuf", "endpoint": "http://localhost:4318/v1/metrics", "certificate": "/app/cert.pem", "client_key": "/app/cert.pem", "client_certificate": "/app/cert.pem", "headers": [ { "name": "api-key", "value": "1234" } ], "headers_list": "api-key=1234", "compression": "gzip", "timeout": 10000, "insecure": false, "temporality_preference": "delta", "default_histogram_aggregation": "base2_exponential_bucket_histogram" } } }, "producers": [ { "prometheus": {} } ] }, { "periodic": { "exporter": { "console": {} } } } ], "views": [ { "selector": { "instrument_name": "my-instrument", "instrument_type": "histogram", "unit": "ms", "meter_name": "my-meter", "meter_version": "1.0.0", "meter_schema_url": "https://opentelemetry.io/schemas/1.16.0" }, "stream": { "name": "new_instrument_name", "description": "new_description", "aggregation": { "explicit_bucket_histogram": { "boundaries": [ 0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000 ], "record_min_max": true } }, "attribute_keys": { "included": [ "key1", "key2" ], "excluded": [ "key3" ] } } } ] }, "propagator": { "composite": [ "tracecontext", "baggage", "b3", "b3multi", "jaeger", "xray", "ottrace" ] }, "tracer_provider": { "processors": [ { "batch": { "schedule_delay": 5000, "export_timeout": 30000, "max_queue_size": 2048, "max_export_batch_size": 512, "exporter": { "otlp": { "protocol": "http/protobuf", "endpoint": "http://localhost:4318/v1/traces", "certificate": "/app/cert.pem", "client_key": "/app/cert.pem", "client_certificate": "/app/cert.pem", "headers": [ { "name": "api-key", "value": "1234" } ], "headers_list": "api-key=1234", "compression": "gzip", "timeout": 10000, "insecure": false } } } }, { "batch": { "exporter": { "zipkin": { "endpoint": "http://localhost:9411/api/v2/spans", "timeout": 10000 } } } }, { "simple": { "exporter": { "console": {} } } } ], "limits": { "attribute_value_length_limit": 4096, "attribute_count_limit": 128, "event_count_limit": 128, "link_count_limit": 128, "event_attribute_count_limit": 128, "link_attribute_count_limit": 128 }, "sampler": { "parent_based": { "root": { "trace_id_ratio_based": { "ratio": 0.0001 } }, "remote_parent_sampled": { "always_on": {} }, "remote_parent_not_sampled": { "always_off": {} }, "local_parent_sampled": { "always_on": {} }, "local_parent_not_sampled": { "always_off": {} } } } }, "resource": { "attributes": [ { "name": "service.name", "value": "unknown_service" }, { "name": "string_key", "value": "value", "type": "string" }, { "name": "bool_key", "value": true, "type": "bool" }, { "name": "int_key", "value": 1, "type": "int" }, { "name": "double_key", "value": 1.1, "type": "double" }, { "name": "string_array_key", "value": [ "value1", "value2" ], "type": "string_array" }, { "name": "bool_array_key", "value": [ true, false ], "type": "bool_array" }, { "name": "int_array_key", "value": [ 1, 2 ], "type": "int_array" }, { "name": "double_array_key", "value": [ 1.1, 2.2 ], "type": "double_array" } ], "attributes_list": "service.namespace=my-namespace,service.version=1.0.0", "detectors": { "attributes": { "included": [ "process.*" ], "excluded": [ "process.command_args" ] } }, "schema_url": "https://opentelemetry.io/schemas/1.16.0" }, "instrumentation": { "general": { "peer": { "service_mapping": [ { "peer": "1.2.3.4", "service": "FooService" }, { "peer": "2.3.4.5", "service": "BarService" } ] }, "http": { "client": { "request_captured_headers": [ "Content-Type", "Accept" ], "response_captured_headers": [ "Content-Type", "Content-Encoding" ] }, "server": { "request_captured_headers": [ "Content-Type", "Accept" ], "response_captured_headers": [ "Content-Type", "Content-Encoding" ] } } }, "cpp": { "example": { "property": "value" } }, "dotnet": { "example": { "property": "value" } }, "erlang": { "example": { "property": "value" } }, "go": { "example": { "property": "value" } }, "java": { "example": { "property": "value" } }, "js": { "example": { "property": "value" } }, "php": { "example": { "property": "value" } }, "python": { "example": { "property": "value" } }, "ruby": { "example": { "property": "value" } }, "rust": { "example": { "property": "value" } }, "swift": { "example": { "property": "value" } } } }open-telemetry-opentelemetry-go-contrib-e5abccb/config/testdata/v0.3.yaml000066400000000000000000000502371470323427300267100ustar00rootroot00000000000000# kitchen-sink.yaml demonstrates all configurable surface area, including explanatory comments. # # It DOES NOT represent expected real world configuration, as it makes strange configuration # choices in an effort to exercise the full surface area. # # Configuration values are set to their defaults when default values are defined. # The file format version. file_format: "0.3" # Configure if the SDK is disabled or not. This is not required to be provided to ensure the SDK isn't disabled, the default value when this is not provided is for the SDK to be enabled. disabled: false # Configure general attribute limits. See also tracer_provider.limits, logger_provider.limits. attribute_limits: # Configure max attribute value size. attribute_value_length_limit: 4096 # Configure max attribute count. attribute_count_limit: 128 # Configure logger provider. logger_provider: # Configure log record processors. processors: - # Configure a batch log record processor. batch: # Configure delay interval (in milliseconds) between two consecutive exports. schedule_delay: 5000 # Configure maximum allowed time (in milliseconds) to export data. export_timeout: 30000 # Configure maximum queue size. max_queue_size: 2048 # Configure maximum batch size. max_export_batch_size: 512 # Configure exporter. exporter: # Configure exporter to be OTLP. otlp: # Configure protocol. protocol: http/protobuf # Configure endpoint. endpoint: http://localhost:4318/v1/logs # Configure certificate. certificate: /app/cert.pem # Configure mTLS private client key. client_key: /app/cert.pem # Configure mTLS client certificate. client_certificate: /app/cert.pem # Configure headers. Entries have higher priority than entries from .headers_list. headers: - name: api-key value: "1234" # Configure headers. Entries have lower priority than entries from .headers. # The value is a list of comma separated key-value pairs matching the format of OTEL_EXPORTER_OTLP_HEADERS. See https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md#configuration-options for details. headers_list: "api-key=1234" # Configure compression. compression: gzip # Configure max time (in milliseconds) to wait for each export. timeout: 10000 # Configure client transport security for the exporter's connection. insecure: false - # Configure a simple log record processor. simple: # Configure exporter. exporter: # Configure exporter to be console. console: {} # Configure log record limits. See also attribute_limits. limits: # Configure max attribute value size. Overrides .attribute_limits.attribute_value_length_limit. attribute_value_length_limit: 4096 # Configure max attribute count. Overrides .attribute_limits.attribute_count_limit. attribute_count_limit: 128 # Configure meter provider. meter_provider: # Configure metric readers. readers: - # Configure a pull based metric reader. pull: # Configure exporter. exporter: # Configure exporter to be prometheus. prometheus: # Configure host. host: localhost # Configure port. port: 9464 # Configure Prometheus Exporter to produce metrics without a unit suffix or UNIT metadata. without_units: false # Configure Prometheus Exporter to produce metrics without a type suffix. without_type_suffix: false # Configure Prometheus Exporter to produce metrics without a scope info metric. without_scope_info: false # Configure Prometheus Exporter to add resource attributes as metrics attributes. with_resource_constant_labels: # Configure resource attributes to be included. If not set, no resource attributes are included. # Attribute keys from resources are evaluated to match as follows: # * If the value of the attribute key exactly matches. # * If the value of the attribute key matches the wildcard pattern, where '?' matches any single character and '*' matches any number of characters including none. included: - "service*" # Configure resource attributes to be excluded. Applies after .with_resource_constant_labels.included (i.e. excluded has higher priority than included). # Attribute keys from resources are evaluated to match as follows: # * If the value of the attribute key exactly matches. # * If the value of the attribute key matches the wildcard pattern, where '?' matches any single character and '*' matches any number of characters including none. excluded: - "service.attr1" # Configure metric producers. producers: - # Configure metric producer to be opencensus. opencensus: {} - # Configure a periodic metric reader. periodic: # Configure delay interval (in milliseconds) between start of two consecutive exports. interval: 5000 # Configure maximum allowed time (in milliseconds) to export data. timeout: 30000 # Configure exporter. exporter: # Configure exporter to be OTLP. otlp: # Configure protocol. protocol: http/protobuf # Configure endpoint. endpoint: http://localhost:4318/v1/metrics # Configure certificate. certificate: /app/cert.pem # Configure mTLS private client key. client_key: /app/cert.pem # Configure mTLS client certificate. client_certificate: /app/cert.pem # Configure headers. Entries have higher priority than entries from .headers_list. headers: - name: api-key value: "1234" # Configure headers. Entries have lower priority than entries from .headers. # The value is a list of comma separated key-value pairs matching the format of OTEL_EXPORTER_OTLP_HEADERS. See https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md#configuration-options for details. headers_list: "api-key=1234" # Configure compression. compression: gzip # Configure max time (in milliseconds) to wait for each export. timeout: 10000 # Configure client transport security for the exporter's connection. insecure: false # Configure temporality preference. temporality_preference: delta # Configure default histogram aggregation. default_histogram_aggregation: base2_exponential_bucket_histogram # Configure metric producers. producers: - # Configure metric producer to be prometheus. prometheus: {} - # Configure a periodic metric reader. periodic: # Configure exporter. exporter: # Configure exporter to be console. console: {} # Configure views. Each view has a selector which determines the instrument(s) it applies to, and a configuration for the resulting stream(s). views: - # Configure view selector. selector: # Configure instrument name selection criteria. instrument_name: my-instrument # Configure instrument type selection criteria. instrument_type: histogram # Configure the instrument unit selection criteria. unit: ms # Configure meter name selection criteria. meter_name: my-meter # Configure meter version selection criteria. meter_version: 1.0.0 # Configure meter schema url selection criteria. meter_schema_url: https://opentelemetry.io/schemas/1.16.0 # Configure view stream. stream: # Configure metric name of the resulting stream(s). name: new_instrument_name # Configure metric description of the resulting stream(s). description: new_description # Configure aggregation of the resulting stream(s). Known values include: default, drop, explicit_bucket_histogram, base2_exponential_bucket_histogram, last_value, sum. aggregation: # Configure aggregation to be explicit_bucket_histogram. explicit_bucket_histogram: # Configure bucket boundaries. boundaries: [ 0.0, 5.0, 10.0, 25.0, 50.0, 75.0, 100.0, 250.0, 500.0, 750.0, 1000.0, 2500.0, 5000.0, 7500.0, 10000.0 ] # Configure record min and max. record_min_max: true # Configure attribute keys retained in the resulting stream(s). attribute_keys: # Configure list of attribute keys to include in the resulting stream(s). All other attributes are dropped. If not set, stream attributes are not configured. included: - key1 - key2 # Configure list of attribute keys to exclude from the resulting stream(s). Applies after .attribute_keys.included (i.e. excluded has higher priority than included). excluded: - key3 # Configure text map context propagators. propagator: # Configure the set of propagators to include in the composite text map propagator. composite: [ tracecontext, baggage, b3, b3multi, jaeger, xray, ottrace ] # Configure tracer provider. tracer_provider: # Configure span processors. processors: - # Configure a batch span processor. batch: # Configure delay interval (in milliseconds) between two consecutive exports. schedule_delay: 5000 # Configure maximum allowed time (in milliseconds) to export data. export_timeout: 30000 # Configure maximum queue size. max_queue_size: 2048 # Configure maximum batch size. max_export_batch_size: 512 # Configure exporter. exporter: # Configure exporter to be OTLP. otlp: # Configure protocol. protocol: http/protobuf # Configure endpoint. endpoint: http://localhost:4318/v1/traces # Configure certificate. certificate: /app/cert.pem # Configure mTLS private client key. client_key: /app/cert.pem # Configure mTLS client certificate. client_certificate: /app/cert.pem # Configure headers. Entries have higher priority than entries from .headers_list. headers: - name: api-key value: "1234" # Configure headers. Entries have lower priority than entries from .headers. # The value is a list of comma separated key-value pairs matching the format of OTEL_EXPORTER_OTLP_HEADERS. See https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md#configuration-options for details. headers_list: "api-key=1234" # Configure compression. compression: gzip # Configure max time (in milliseconds) to wait for each export. timeout: 10000 # Configure client transport security for the exporter's connection. insecure: false - # Configure a batch span processor. batch: # Configure exporter. exporter: # Configure exporter to be zipkin. zipkin: # Configure endpoint. endpoint: http://localhost:9411/api/v2/spans # Configure max time (in milliseconds) to wait for each export. timeout: 10000 - # Configure a simple span processor. simple: # Configure exporter. exporter: # Configure exporter to be console. console: {} # Configure span limits. See also attribute_limits. limits: # Configure max attribute value size. Overrides .attribute_limits.attribute_value_length_limit. attribute_value_length_limit: 4096 # Configure max attribute count. Overrides .attribute_limits.attribute_count_limit. attribute_count_limit: 128 # Configure max span event count. event_count_limit: 128 # Configure max span link count. link_count_limit: 128 # Configure max attributes per span event. event_attribute_count_limit: 128 # Configure max attributes per span link. link_attribute_count_limit: 128 # Configure the sampler. sampler: # Configure sampler to be parent_based. parent_based: # Configure root sampler. root: # Configure sampler to be trace_id_ratio_based. trace_id_ratio_based: # Configure trace_id_ratio. ratio: 0.0001 # Configure remote_parent_sampled sampler. remote_parent_sampled: # Configure sampler to be always_on. always_on: {} # Configure remote_parent_not_sampled sampler. remote_parent_not_sampled: # Configure sampler to be always_off. always_off: {} # Configure local_parent_sampled sampler. local_parent_sampled: # Configure sampler to be always_on. always_on: {} # Configure local_parent_not_sampled sampler. local_parent_not_sampled: # Configure sampler to be always_off. always_off: {} # Configure resource for all signals. resource: # Configure resource attributes. Entries have higher priority than entries from .resource.attributes_list. # Entries must contain .name nand .value, and may optionally include .type, which defaults ot "string" if not set. The value must match the type. Values for .type include: string, bool, int, double, string_array, bool_array, int_array, double_array. attributes: - name: service.name value: unknown_service - name: string_key value: value type: string - name: bool_key value: true type: bool - name: int_key value: 1 type: int - name: double_key value: 1.1 type: double - name: string_array_key value: [ "value1", "value2" ] type: string_array - name: bool_array_key value: [ true, false ] type: bool_array - name: int_array_key value: [ 1, 2 ] type: int_array - name: double_array_key value: [ 1.1, 2.2 ] type: double_array # Configure resource attributes. Entries have lower priority than entries from .resource.attributes. # The value is a list of comma separated key-value pairs matching the format of OTEL_RESOURCE_ATTRIBUTES. See https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/configuration/sdk-environment-variables.md#general-sdk-configuration for details. attributes_list: "service.namespace=my-namespace,service.version=1.0.0" # Configure resource detectors. detectors: # Configure attributes provided by resource detectors. attributes: # Configure list of attribute key patterns to include from resource detectors. If not set, all attributes are included. # Attribute keys from resource detectors are evaluated to match as follows: # * If the value of the attribute key exactly matches. # * If the value of the attribute key matches the wildcard pattern, where '?' matches any single character and '*' matches any number of characters including none. included: - process.* # Configure list of attribute key patterns to exclude from resource detectors. Applies after .resource.detectors.attributes.included (i.e. excluded has higher priority than included). # Attribute keys from resource detectors are evaluated to match as follows: # * If the value of the attribute key exactly matches. # * If the value of the attribute key matches the wildcard pattern, where '?' matches any single character and '*' matches any number of characters including none. excluded: - process.command_args # Configure resource schema URL. schema_url: https://opentelemetry.io/schemas/1.16.0 # Configure instrumentation. instrumentation: # Configure general SemConv options that may apply to multiple languages and instrumentations. # Instrumenation may merge general config options with the language specific configuration at .instrumentation.. general: # Configure instrumentations following the peer semantic conventions. # See peer semantic conventions: https://opentelemetry.io/docs/specs/semconv/attributes-registry/peer/ peer: # Configure the service mapping for instrumentations following peer.service semantic conventions. # Each entry is a key value pair where "peer" defines the IP address and "service" defines the corresponding logical name of the service. # See peer.service semantic conventions: https://opentelemetry.io/docs/specs/semconv/general/attributes/#general-remote-service-attributes service_mapping: - peer: 1.2.3.4 service: FooService - peer: 2.3.4.5 service: BarService # Configure instrumentations following the http semantic conventions. # See http semantic conventions: https://opentelemetry.io/docs/specs/semconv/http/ http: # Configure instrumentations following the http client semantic conventions. client: # Configure headers to capture for outbound http requests. request_captured_headers: - Content-Type - Accept # Configure headers to capture for outbound http responses. response_captured_headers: - Content-Type - Content-Encoding # Configure instrumentations following the http server semantic conventions. server: # Configure headers to capture for inbound http requests. request_captured_headers: - Content-Type - Accept # Configure headers to capture for outbound http responses. response_captured_headers: - Content-Type - Content-Encoding # Configure C++ language-specific instrumentation libraries. cpp: # Configure the instrumentation corresponding to key "example". example: property: "value" # Configure .NET language-specific instrumentation libraries. dotnet: # Configure the instrumentation corresponding to key "example". example: property: "value" # Configure Erlang language-specific instrumentation libraries. erlang: # Configure the instrumentation corresponding to key "example". example: property: "value" # Configure Go language-specific instrumentation libraries. go: # Configure the instrumentation corresponding to key "example". example: property: "value" # Configure Java language-specific instrumentation libraries. java: # Configure the instrumentation corresponding to key "example". example: property: "value" # Configure JavaScript language-specific instrumentation libraries. js: # Configure the instrumentation corresponding to key "example". example: property: "value" # Configure PHP language-specific instrumentation libraries. php: # Configure the instrumentation corresponding to key "example". example: property: "value" # Configure Python language-specific instrumentation libraries. python: # Configure the instrumentation corresponding to key "example". example: property: "value" # Configure Ruby language-specific instrumentation libraries. ruby: # Configure the instrumentation corresponding to key "example". example: property: "value" # Configure Rust language-specific instrumentation libraries. rust: # Configure the instrumentation corresponding to key "example". example: property: "value" # Configure Swift language-specific instrumentation libraries. swift: # Configure the instrumentation corresponding to key "example". example: property: "value" open-telemetry-opentelemetry-go-contrib-e5abccb/config/testdata/valid_empty.json000066400000000000000000000000511470323427300305330ustar00rootroot00000000000000{"file_format": "0.1", "disabled": false}open-telemetry-opentelemetry-go-contrib-e5abccb/config/testdata/valid_empty.yaml000066400000000000000000000000401470323427300305220ustar00rootroot00000000000000file_format: 0.1 disabled: falseopen-telemetry-opentelemetry-go-contrib-e5abccb/config/trace.go000066400000000000000000000145231470323427300251500ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package config // import "go.opentelemetry.io/contrib/config" import ( "context" "errors" "fmt" "net/url" "time" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace/noop" ) func tracerProvider(cfg configOptions, res *resource.Resource) (trace.TracerProvider, shutdownFunc, error) { if cfg.opentelemetryConfig.TracerProvider == nil { return noop.NewTracerProvider(), noopShutdown, nil } opts := []sdktrace.TracerProviderOption{ sdktrace.WithResource(res), } var errs []error for _, processor := range cfg.opentelemetryConfig.TracerProvider.Processors { sp, err := spanProcessor(cfg.ctx, processor) if err == nil { opts = append(opts, sdktrace.WithSpanProcessor(sp)) } else { errs = append(errs, err) } } if len(errs) > 0 { return noop.NewTracerProvider(), noopShutdown, errors.Join(errs...) } tp := sdktrace.NewTracerProvider(opts...) return tp, tp.Shutdown, nil } func spanExporter(ctx context.Context, exporter SpanExporter) (sdktrace.SpanExporter, error) { if exporter.Console != nil && exporter.OTLP != nil { return nil, errors.New("must not specify multiple exporters") } if exporter.Console != nil { return stdouttrace.New( stdouttrace.WithPrettyPrint(), ) } if exporter.OTLP != nil && exporter.OTLP.Protocol != nil { switch *exporter.OTLP.Protocol { case protocolProtobufHTTP: return otlpHTTPSpanExporter(ctx, exporter.OTLP) case protocolProtobufGRPC: return otlpGRPCSpanExporter(ctx, exporter.OTLP) default: return nil, fmt.Errorf("unsupported protocol %q", *exporter.OTLP.Protocol) } } return nil, errors.New("no valid span exporter") } func spanProcessor(ctx context.Context, processor SpanProcessor) (sdktrace.SpanProcessor, error) { if processor.Batch != nil && processor.Simple != nil { return nil, errors.New("must not specify multiple span processor type") } if processor.Batch != nil { exp, err := spanExporter(ctx, processor.Batch.Exporter) if err != nil { return nil, err } return batchSpanProcessor(processor.Batch, exp) } if processor.Simple != nil { exp, err := spanExporter(ctx, processor.Simple.Exporter) if err != nil { return nil, err } return sdktrace.NewSimpleSpanProcessor(exp), nil } return nil, fmt.Errorf("unsupported span processor type, must be one of simple or batch") } func otlpGRPCSpanExporter(ctx context.Context, otlpConfig *OTLP) (sdktrace.SpanExporter, error) { var opts []otlptracegrpc.Option if otlpConfig.Endpoint != nil { u, err := url.ParseRequestURI(*otlpConfig.Endpoint) if err != nil { return nil, err } // ParseRequestURI leaves the Host field empty when no // scheme is specified (i.e. localhost:4317). This check is // here to support the case where a user may not specify a // scheme. The code does its best effort here by using // otlpConfig.Endpoint as-is in that case. if u.Host != "" { opts = append(opts, otlptracegrpc.WithEndpoint(u.Host)) } else { opts = append(opts, otlptracegrpc.WithEndpoint(*otlpConfig.Endpoint)) } if u.Scheme == "http" { opts = append(opts, otlptracegrpc.WithInsecure()) } } if otlpConfig.Compression != nil { switch *otlpConfig.Compression { case compressionGzip: opts = append(opts, otlptracegrpc.WithCompressor(*otlpConfig.Compression)) case compressionNone: // none requires no options default: return nil, fmt.Errorf("unsupported compression %q", *otlpConfig.Compression) } } if otlpConfig.Timeout != nil && *otlpConfig.Timeout > 0 { opts = append(opts, otlptracegrpc.WithTimeout(time.Millisecond*time.Duration(*otlpConfig.Timeout))) } if len(otlpConfig.Headers) > 0 { opts = append(opts, otlptracegrpc.WithHeaders(toStringMap(otlpConfig.Headers))) } return otlptracegrpc.New(ctx, opts...) } func otlpHTTPSpanExporter(ctx context.Context, otlpConfig *OTLP) (sdktrace.SpanExporter, error) { var opts []otlptracehttp.Option if otlpConfig.Endpoint != nil { u, err := url.ParseRequestURI(*otlpConfig.Endpoint) if err != nil { return nil, err } opts = append(opts, otlptracehttp.WithEndpoint(u.Host)) if u.Scheme == "http" { opts = append(opts, otlptracehttp.WithInsecure()) } if len(u.Path) > 0 { opts = append(opts, otlptracehttp.WithURLPath(u.Path)) } } if otlpConfig.Compression != nil { switch *otlpConfig.Compression { case compressionGzip: opts = append(opts, otlptracehttp.WithCompression(otlptracehttp.GzipCompression)) case compressionNone: opts = append(opts, otlptracehttp.WithCompression(otlptracehttp.NoCompression)) default: return nil, fmt.Errorf("unsupported compression %q", *otlpConfig.Compression) } } if otlpConfig.Timeout != nil && *otlpConfig.Timeout > 0 { opts = append(opts, otlptracehttp.WithTimeout(time.Millisecond*time.Duration(*otlpConfig.Timeout))) } if len(otlpConfig.Headers) > 0 { opts = append(opts, otlptracehttp.WithHeaders(toStringMap(otlpConfig.Headers))) } return otlptracehttp.New(ctx, opts...) } func batchSpanProcessor(bsp *BatchSpanProcessor, exp sdktrace.SpanExporter) (sdktrace.SpanProcessor, error) { var opts []sdktrace.BatchSpanProcessorOption if bsp.ExportTimeout != nil { if *bsp.ExportTimeout < 0 { return nil, fmt.Errorf("invalid export timeout %d", *bsp.ExportTimeout) } opts = append(opts, sdktrace.WithExportTimeout(time.Millisecond*time.Duration(*bsp.ExportTimeout))) } if bsp.MaxExportBatchSize != nil { if *bsp.MaxExportBatchSize < 0 { return nil, fmt.Errorf("invalid batch size %d", *bsp.MaxExportBatchSize) } opts = append(opts, sdktrace.WithMaxExportBatchSize(*bsp.MaxExportBatchSize)) } if bsp.MaxQueueSize != nil { if *bsp.MaxQueueSize < 0 { return nil, fmt.Errorf("invalid queue size %d", *bsp.MaxQueueSize) } opts = append(opts, sdktrace.WithMaxQueueSize(*bsp.MaxQueueSize)) } if bsp.ScheduleDelay != nil { if *bsp.ScheduleDelay < 0 { return nil, fmt.Errorf("invalid schedule delay %d", *bsp.ScheduleDelay) } opts = append(opts, sdktrace.WithBatchTimeout(time.Millisecond*time.Duration(*bsp.ScheduleDelay))) } return sdktrace.NewBatchSpanProcessor(exp, opts...), nil } open-telemetry-opentelemetry-go-contrib-e5abccb/config/trace_test.go000066400000000000000000000341431470323427300262070ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package config import ( "context" "errors" "net/url" "reflect" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace/noop" ) func TestTracerPovider(t *testing.T) { tests := []struct { name string cfg configOptions wantProvider trace.TracerProvider wantErr error }{ { name: "no-tracer-provider-configured", wantProvider: noop.NewTracerProvider(), }, { name: "error-in-config", cfg: configOptions{ opentelemetryConfig: OpenTelemetryConfiguration{ TracerProvider: &TracerProvider{ Processors: []SpanProcessor{ { Batch: &BatchSpanProcessor{}, Simple: &SimpleSpanProcessor{}, }, }, }, }, }, wantProvider: noop.NewTracerProvider(), wantErr: errors.Join(errors.New("must not specify multiple span processor type")), }, { name: "multiple-errors-in-config", cfg: configOptions{ opentelemetryConfig: OpenTelemetryConfiguration{ TracerProvider: &TracerProvider{ Processors: []SpanProcessor{ { Batch: &BatchSpanProcessor{}, Simple: &SimpleSpanProcessor{}, }, { Simple: &SimpleSpanProcessor{ Exporter: SpanExporter{ Console: Console{}, OTLP: &OTLP{}, }, }, }, }, }, }, }, wantProvider: noop.NewTracerProvider(), wantErr: errors.Join(errors.New("must not specify multiple span processor type"), errors.New("must not specify multiple exporters")), }, } for _, tt := range tests { tp, shutdown, err := tracerProvider(tt.cfg, resource.Default()) require.Equal(t, tt.wantProvider, tp) assert.Equal(t, tt.wantErr, err) require.NoError(t, shutdown(context.Background())) } } func TestSpanProcessor(t *testing.T) { consoleExporter, err := stdouttrace.New( stdouttrace.WithPrettyPrint(), ) require.NoError(t, err) ctx := context.Background() otlpGRPCExporter, err := otlptracegrpc.New(ctx) require.NoError(t, err) otlpHTTPExporter, err := otlptracehttp.New(ctx) require.NoError(t, err) testCases := []struct { name string processor SpanProcessor args any wantErr error wantProcessor sdktrace.SpanProcessor }{ { name: "no processor", wantErr: errors.New("unsupported span processor type, must be one of simple or batch"), }, { name: "multiple processor types", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ Exporter: SpanExporter{}, }, Simple: &SimpleSpanProcessor{}, }, wantErr: errors.New("must not specify multiple span processor type"), }, { name: "batch processor invalid exporter", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ Exporter: SpanExporter{}, }, }, wantErr: errors.New("no valid span exporter"), }, { name: "batch processor invalid batch size console exporter", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(-1), Exporter: SpanExporter{ Console: Console{}, }, }, }, wantErr: errors.New("invalid batch size -1"), }, { name: "batch processor invalid export timeout console exporter", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ ExportTimeout: ptr(-2), Exporter: SpanExporter{ Console: Console{}, }, }, }, wantErr: errors.New("invalid export timeout -2"), }, { name: "batch processor invalid queue size console exporter", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxQueueSize: ptr(-3), Exporter: SpanExporter{ Console: Console{}, }, }, }, wantErr: errors.New("invalid queue size -3"), }, { name: "batch processor invalid schedule delay console exporter", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ ScheduleDelay: ptr(-4), Exporter: SpanExporter{ Console: Console{}, }, }, }, wantErr: errors.New("invalid schedule delay -4"), }, { name: "batch processor with multiple exporters", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ Exporter: SpanExporter{ Console: Console{}, OTLP: &OTLP{}, }, }, }, wantErr: errors.New("must not specify multiple exporters"), }, { name: "batch processor console exporter", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: SpanExporter{ Console: Console{}, }, }, }, wantProcessor: sdktrace.NewBatchSpanProcessor(consoleExporter), }, { name: "batch/otlp-exporter-invalid-protocol", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLP: &OTLP{ Protocol: ptr("http/invalid"), }, }, }, }, wantErr: errors.New("unsupported protocol \"http/invalid\""), }, { name: "batch/otlp-grpc-exporter-no-endpoint", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLP: &OTLP{ Protocol: ptr("grpc/protobuf"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantProcessor: sdktrace.NewBatchSpanProcessor(otlpGRPCExporter), }, { name: "batch/otlp-grpc-exporter", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLP: &OTLP{ Protocol: ptr("grpc/protobuf"), Endpoint: ptr("http://localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantProcessor: sdktrace.NewBatchSpanProcessor(otlpGRPCExporter), }, { name: "batch/otlp-grpc-exporter-no-scheme", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLP: &OTLP{ Protocol: ptr("grpc/protobuf"), Endpoint: ptr("localhost:4317"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantProcessor: sdktrace.NewBatchSpanProcessor(otlpGRPCExporter), }, { name: "batch/otlp-grpc-invalid-endpoint", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLP: &OTLP{ Protocol: ptr("grpc/protobuf"), Endpoint: ptr(" "), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantErr: &url.Error{Op: "parse", URL: " ", Err: errors.New("invalid URI for request")}, }, { name: "batch/otlp-grpc-invalid-compression", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLP: &OTLP{ Protocol: ptr("grpc/protobuf"), Endpoint: ptr("localhost:4317"), Compression: ptr("invalid"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantErr: errors.New("unsupported compression \"invalid\""), }, { name: "batch/otlp-http-exporter", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLP: &OTLP{ Protocol: ptr("http/protobuf"), Endpoint: ptr("http://localhost:4318"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantProcessor: sdktrace.NewBatchSpanProcessor(otlpHTTPExporter), }, { name: "batch/otlp-http-exporter-with-path", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLP: &OTLP{ Protocol: ptr("http/protobuf"), Endpoint: ptr("http://localhost:4318/path/123"), Compression: ptr("none"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantProcessor: sdktrace.NewBatchSpanProcessor(otlpHTTPExporter), }, { name: "batch/otlp-http-exporter-no-endpoint", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLP: &OTLP{ Protocol: ptr("http/protobuf"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantProcessor: sdktrace.NewBatchSpanProcessor(otlpHTTPExporter), }, { name: "batch/otlp-http-exporter-no-scheme", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLP: &OTLP{ Protocol: ptr("http/protobuf"), Endpoint: ptr("localhost:4318"), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantProcessor: sdktrace.NewBatchSpanProcessor(otlpHTTPExporter), }, { name: "batch/otlp-http-invalid-endpoint", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLP: &OTLP{ Protocol: ptr("http/protobuf"), Endpoint: ptr(" "), Compression: ptr("gzip"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantErr: &url.Error{Op: "parse", URL: " ", Err: errors.New("invalid URI for request")}, }, { name: "batch/otlp-http-none-compression", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLP: &OTLP{ Protocol: ptr("http/protobuf"), Endpoint: ptr("localhost:4318"), Compression: ptr("none"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantProcessor: sdktrace.NewBatchSpanProcessor(otlpHTTPExporter), }, { name: "batch/otlp-http-invalid-compression", processor: SpanProcessor{ Batch: &BatchSpanProcessor{ MaxExportBatchSize: ptr(0), ExportTimeout: ptr(0), MaxQueueSize: ptr(0), ScheduleDelay: ptr(0), Exporter: SpanExporter{ OTLP: &OTLP{ Protocol: ptr("http/protobuf"), Endpoint: ptr("localhost:4318"), Compression: ptr("invalid"), Timeout: ptr(1000), Headers: []NameStringValuePair{ {Name: "test", Value: ptr("test1")}, }, }, }, }, }, wantErr: errors.New("unsupported compression \"invalid\""), }, { name: "simple/no-exporter", processor: SpanProcessor{ Simple: &SimpleSpanProcessor{ Exporter: SpanExporter{}, }, }, wantErr: errors.New("no valid span exporter"), }, { name: "simple/console-exporter", processor: SpanProcessor{ Simple: &SimpleSpanProcessor{ Exporter: SpanExporter{ Console: Console{}, }, }, }, wantProcessor: sdktrace.NewSimpleSpanProcessor(consoleExporter), }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { got, err := spanProcessor(context.Background(), tt.processor) require.Equal(t, tt.wantErr, err) if tt.wantProcessor == nil { require.Nil(t, got) } else { require.Equal(t, reflect.TypeOf(tt.wantProcessor), reflect.TypeOf(got)) var fieldName string switch reflect.TypeOf(tt.wantProcessor).String() { case "*trace.simpleSpanProcessor": fieldName = "exporter" default: fieldName = "e" } wantExporterType := reflect.Indirect(reflect.ValueOf(tt.wantProcessor)).FieldByName(fieldName).Elem().Type() gotExporterType := reflect.Indirect(reflect.ValueOf(got)).FieldByName(fieldName).Elem().Type() require.Equal(t, wantExporterType.String(), gotExporterType.String()) } }) } } open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/000077500000000000000000000000001470323427300242455ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/aws/000077500000000000000000000000001470323427300250375ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/aws/README.md000066400000000000000000000025331470323427300263210ustar00rootroot00000000000000# AWS Resource Detectors ## EC2 Sample code snippet to initialize EC2 resource detector ``` // Instantiate a new EC2 Resource detector ec2ResourceDetector := ec2.NewResourceDetector() resource, err := ec2ResourceDetector.Detect(context.Background()) ``` EC2 resource detector captures following EC2 instance environment attributes ``` cloud.region cloud.availability_zone cloud.account.id host.id host.image.id host.type ``` ## ECS Sample code snippet to initialize ECS resource detector ``` // Instantiate a new ECS Resource detector ecsResourceDetector := ecs.NewResourceDetector() resource, err := ecsResourceDetector.Detect(context.Background()) ``` ECS resource detector captures following ECS environment attributes ``` cloud.region cloud.availability_zone cloud.account.id cloud.resource_id container.name container.id aws.ecs.cluster.arn aws.ecs.container.arn aws.ecs.launchtype aws.ecs.task.arn aws.ecs.task.family aws.ecs.task.revision aws.log.group.arns aws.log.group.names aws.log.stream.arns aws.log.stream.names ``` ## EKS Sample code snippet to initialize EKS resource detector ``` // Instantiate a new EKS Resource detector eksResourceDetector := eks.NewResourceDetector() resource, err := eksResourceDetector.Detect(context.Background()) ``` EKS resource detector captures following EKS environment attributes ``` k8s.cluster.name container.id ``` open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/aws/ec2/000077500000000000000000000000001470323427300255105ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/aws/ec2/ec2.go000066400000000000000000000067371470323427300265250ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package ec2 // import "go.opentelemetry.io/contrib/detectors/aws/ec2" import ( "context" "errors" "fmt" "net/http" "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/ec2metadata" "github.com/aws/aws-sdk-go/aws/session" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.26.0" ) type config struct { c Client } // newConfig returns an appropriately configured config. func newConfig(options ...Option) *config { c := new(config) for _, option := range options { option.apply(c) } return c } // Option applies an EC2 detector configuration option. type Option interface { apply(*config) } type optionFunc func(*config) func (fn optionFunc) apply(c *config) { fn(c) } // WithClient sets the ec2metadata client in config. func WithClient(t Client) Option { return optionFunc(func(c *config) { c.c = t }) } func (cfg *config) getClient() Client { return cfg.c } // resource detector collects resource information from EC2 environment. type resourceDetector struct { c Client } // Client implements methods to capture EC2 environment metadata information. type Client interface { Available() bool GetInstanceIdentityDocument() (ec2metadata.EC2InstanceIdentityDocument, error) GetMetadata(p string) (string, error) } // compile time assertion that resourceDetector implements the resource.Detector interface. var _ resource.Detector = (*resourceDetector)(nil) // NewResourceDetector returns a resource detector that will detect AWS EC2 resources. func NewResourceDetector(opts ...Option) resource.Detector { c := newConfig(opts...) return &resourceDetector{c.getClient()} } // Detect detects associated resources when running in AWS environment. func (detector *resourceDetector) Detect(ctx context.Context) (*resource.Resource, error) { client, err := detector.client() if err != nil { return nil, err } if !client.Available() { return nil, nil } doc, err := client.GetInstanceIdentityDocument() if err != nil { return nil, err } attributes := []attribute.KeyValue{ semconv.CloudProviderAWS, semconv.CloudPlatformAWSEC2, semconv.CloudRegion(doc.Region), semconv.CloudAvailabilityZone(doc.AvailabilityZone), semconv.CloudAccountID(doc.AccountID), semconv.HostID(doc.InstanceID), semconv.HostImageID(doc.ImageID), semconv.HostType(doc.InstanceType), } m := &metadata{client: client} m.add(semconv.HostNameKey, "hostname") attributes = append(attributes, m.attributes...) if len(m.errs) > 0 { err = fmt.Errorf("%w: %s", resource.ErrPartialResource, m.errs) } return resource.NewWithAttributes(semconv.SchemaURL, attributes...), err } func (detector *resourceDetector) client() (Client, error) { if detector.c != nil { return detector.c, nil } s, err := session.NewSession() if err != nil { return nil, err } return ec2metadata.New(s), nil } type metadata struct { client Client errs []error attributes []attribute.KeyValue } func (m *metadata) add(k attribute.Key, n string) { v, err := m.client.GetMetadata(n) if err == nil { m.attributes = append(m.attributes, k.String(v)) return } var rf awserr.RequestFailure ok := errors.As(err, &rf) if !ok { m.errs = append(m.errs, fmt.Errorf("%q: %w", n, err)) return } if rf.StatusCode() == http.StatusNotFound { return } m.errs = append(m.errs, fmt.Errorf("%q: %d %s", n, rf.StatusCode(), rf.Code())) } open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/aws/ec2/ec2_test.go000066400000000000000000000123321470323427300275500ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package ec2 import ( "context" "errors" "net/http" "testing" "time" "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/ec2metadata" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.26.0" ) func TestAWS_Detect(t *testing.T) { type fields struct { Client Client } type want struct { Error string Partial bool Resource *resource.Resource } usWestInst := func() (ec2metadata.EC2InstanceIdentityDocument, error) { // Example from https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-identity-documents.html doc := ec2metadata.EC2InstanceIdentityDocument{ MarketplaceProductCodes: []string{"1abc2defghijklm3nopqrs4tu"}, AvailabilityZone: "us-west-2b", PrivateIP: "10.158.112.84", Version: "2017-09-30", Region: "us-west-2", InstanceID: "i-1234567890abcdef0", InstanceType: "t2.micro", AccountID: "123456789012", PendingTime: time.Date(2016, time.November, 19, 16, 32, 11, 0, time.UTC), ImageID: "ami-5fb8c835", Architecture: "x86_64", } return doc, nil } usWestIDLabels := []attribute.KeyValue{ semconv.CloudProviderAWS, semconv.CloudPlatformAWSEC2, semconv.CloudRegion("us-west-2"), semconv.CloudAvailabilityZone("us-west-2b"), semconv.CloudAccountID("123456789012"), semconv.HostID("i-1234567890abcdef0"), semconv.HostImageID("ami-5fb8c835"), semconv.HostType("t2.micro"), } testTable := map[string]struct { Fields fields Want want }{ "Unavailable": { Fields: fields{Client: &clientMock{}}, }, "Instance ID Error": { Fields: fields{ Client: &clientMock{available: true, idDoc: func() (ec2metadata.EC2InstanceIdentityDocument, error) { return ec2metadata.EC2InstanceIdentityDocument{}, errors.New("id not available") }}, }, Want: want{Error: "id not available"}, }, "Hostname Not Found": { Fields: fields{ Client: &clientMock{available: true, idDoc: usWestInst, metadata: map[string]meta{}}, }, Want: want{Resource: resource.NewWithAttributes(semconv.SchemaURL, usWestIDLabels...)}, }, "Hostname Response Error": { Fields: fields{ Client: &clientMock{ available: true, idDoc: usWestInst, metadata: map[string]meta{ "hostname": {err: awserr.NewRequestFailure(awserr.New("EC2MetadataError", "failed to make EC2Metadata request", errors.New("response error")), http.StatusInternalServerError, "test-request")}, }, }, }, Want: want{ Error: `partial resource: ["hostname": 500 EC2MetadataError]`, Partial: true, Resource: resource.NewWithAttributes(semconv.SchemaURL, usWestIDLabels...), }, }, "Hostname General Error": { Fields: fields{ Client: &clientMock{ available: true, idDoc: usWestInst, metadata: map[string]meta{ "hostname": {err: errors.New("unknown error")}, }, }, }, Want: want{ Error: `partial resource: ["hostname": unknown error]`, Partial: true, Resource: resource.NewWithAttributes(semconv.SchemaURL, usWestIDLabels...), }, }, "All Available": { Fields: fields{ Client: &clientMock{ available: true, idDoc: usWestInst, metadata: map[string]meta{ "hostname": {value: "ip-12-34-56-78.us-west-2.compute.internal"}, }, }, }, Want: want{Resource: resource.NewWithAttributes( semconv.SchemaURL, semconv.CloudProviderAWS, semconv.CloudPlatformAWSEC2, semconv.CloudRegion("us-west-2"), semconv.CloudAvailabilityZone("us-west-2b"), semconv.CloudAccountID("123456789012"), semconv.HostID("i-1234567890abcdef0"), semconv.HostImageID("ami-5fb8c835"), semconv.HostName("ip-12-34-56-78.us-west-2.compute.internal"), semconv.HostType("t2.micro"), )}, }, } for name, tt := range testTable { tt := tt t.Run(name, func(t *testing.T) { t.Parallel() ec2ResourceDetector := NewResourceDetector(WithClient(tt.Fields.Client)) r, err := ec2ResourceDetector.Detect(context.Background()) assert.Equal(t, tt.Want.Resource, r, "Resource") if tt.Want.Error != "" { require.EqualError(t, err, tt.Want.Error, "Error") assert.Equal(t, tt.Want.Partial, errors.Is(err, resource.ErrPartialResource), "Partial Resource") return } require.NoError(t, err, "Error") }) } } type clientMock struct { available bool idDoc func() (ec2metadata.EC2InstanceIdentityDocument, error) metadata map[string]meta } type meta struct { err error value string } func (c *clientMock) Available() bool { return c.available } func (c *clientMock) GetInstanceIdentityDocument() (ec2metadata.EC2InstanceIdentityDocument, error) { return c.idDoc() } func (c *clientMock) GetMetadata(p string) (string, error) { v, ok := c.metadata[p] if !ok { return "", awserr.NewRequestFailure(awserr.New("EC2MetadataError", "failed to make EC2Metadata request", errors.New("response error")), http.StatusNotFound, "test-request") } return v.value, v.err } open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/aws/ec2/go.mod000066400000000000000000000012631470323427300266200ustar00rootroot00000000000000module go.opentelemetry.io/contrib/detectors/aws/ec2 go 1.22 require ( github.com/aws/aws-sdk-go v1.55.5 github.com/stretchr/testify v1.9.0 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/sdk v1.31.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect go.opentelemetry.io/otel/trace v1.31.0 // indirect golang.org/x/sys v0.26.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/aws/ec2/go.sum000066400000000000000000000064701470323427300266520ustar00rootroot00000000000000github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU= github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/aws/ec2/version.go000066400000000000000000000007701470323427300275300ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package ec2 // import "go.opentelemetry.io/contrib/detectors/aws/ec2" // Version is the current release version of the EC2 resource detector. func Version() string { return "1.31.0" // This string is updated by the pre_release.sh script during release } // SemVersion is the semantic version to be supplied to tracer/meter creation. // // Deprecated: Use [Version] instead. func SemVersion() string { return Version() } open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/aws/ecs/000077500000000000000000000000001470323427300256115ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/aws/ecs/ecs.go000066400000000000000000000176231470323427300267230ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package ecs // import "go.opentelemetry.io/contrib/detectors/aws/ecs" import ( "context" "errors" "fmt" "net/http" "os" "runtime" "strings" ecsmetadata "github.com/brunoscheufler/aws-ecs-metadata-go" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.26.0" ) const ( // TypeStr is AWS ECS type. TypeStr = "ecs" metadataV3EnvVar = "ECS_CONTAINER_METADATA_URI" metadataV4EnvVar = "ECS_CONTAINER_METADATA_URI_V4" containerIDLength = 64 defaultCgroupPath = "/proc/self/cgroup" ) var ( empty = resource.Empty() errCannotReadContainerName = errors.New("failed to read hostname") errCannotParseTaskArn = errors.New("cannot parse region and account ID from the Task's ARN: the ARN does not contain at least 6 segments separated by the ':' character") errCannotRetrieveLogsGroupMetadataV4 = errors.New("the ECS Metadata v4 did not return a AwsLogGroup name") errCannotRetrieveLogsStreamMetadataV4 = errors.New("the ECS Metadata v4 did not return a AwsLogStream name") ) // Create interface for methods needing to be mocked. type detectorUtils interface { getContainerName() (string, error) getContainerID() (string, error) getContainerMetadataV4(ctx context.Context) (*ecsmetadata.ContainerMetadataV4, error) getTaskMetadataV4(ctx context.Context) (*ecsmetadata.TaskMetadataV4, error) } // struct implements detectorUtils interface. type ecsDetectorUtils struct{} // resource detector collects resource information from Elastic Container Service environment. type resourceDetector struct { utils detectorUtils } // compile time assertion that ecsDetectorUtils implements detectorUtils interface. var _ detectorUtils = (*ecsDetectorUtils)(nil) // compile time assertion that resource detector implements the resource.Detector interface. var _ resource.Detector = (*resourceDetector)(nil) // NewResourceDetector returns a resource detector that will detect AWS ECS resources. func NewResourceDetector() resource.Detector { return &resourceDetector{ utils: ecsDetectorUtils{}, } } // Detect finds associated resources when running on ECS environment. func (detector *resourceDetector) Detect(ctx context.Context) (*resource.Resource, error) { metadataURIV3 := os.Getenv(metadataV3EnvVar) metadataURIV4 := os.Getenv(metadataV4EnvVar) if len(metadataURIV3) == 0 && len(metadataURIV4) == 0 { return nil, nil } hostName, err := detector.utils.getContainerName() if err != nil { return empty, err } containerID, err := detector.utils.getContainerID() if err != nil { return empty, err } attributes := []attribute.KeyValue{ semconv.CloudProviderAWS, semconv.CloudPlatformAWSECS, semconv.ContainerName(hostName), semconv.ContainerID(containerID), } if len(metadataURIV4) > 0 { containerMetadata, err := detector.utils.getContainerMetadataV4(ctx) if err != nil { return empty, err } taskMetadata, err := detector.utils.getTaskMetadataV4(ctx) if err != nil { return empty, err } baseArn := detector.getBaseArn( taskMetadata.TaskARN, containerMetadata.ContainerARN, taskMetadata.Cluster, ) if baseArn != "" { if !strings.HasPrefix(taskMetadata.Cluster, "arn:") { taskMetadata.Cluster = fmt.Sprintf("%s:cluster/%s", baseArn, taskMetadata.Cluster) } if !strings.HasPrefix(containerMetadata.ContainerARN, "arn:") { containerMetadata.ContainerARN = fmt.Sprintf("%s:container/%s", baseArn, containerMetadata.ContainerARN) } if !strings.HasPrefix(taskMetadata.TaskARN, "arn:") { taskMetadata.TaskARN = fmt.Sprintf("%s:task/%s", baseArn, taskMetadata.TaskARN) } } arnParts := strings.Split(taskMetadata.TaskARN, ":") // A valid ARN should have at least 6 parts. if len(arnParts) < 6 { return empty, errCannotParseTaskArn } attributes = append( attributes, semconv.CloudRegion(arnParts[3]), semconv.CloudAccountID(arnParts[4]), ) availabilityZone := taskMetadata.AvailabilityZone if len(availabilityZone) > 0 { attributes = append( attributes, semconv.CloudAvailabilityZone(availabilityZone), ) } logAttributes, err := detector.getLogsAttributes(containerMetadata) if err != nil { return empty, err } if len(logAttributes) > 0 { attributes = append(attributes, logAttributes...) } attributes = append( attributes, semconv.CloudResourceID(containerMetadata.ContainerARN), semconv.AWSECSContainerARN(containerMetadata.ContainerARN), semconv.AWSECSClusterARN(taskMetadata.Cluster), semconv.AWSECSLaunchtypeKey.String(strings.ToLower(taskMetadata.LaunchType)), semconv.AWSECSTaskARN(taskMetadata.TaskARN), semconv.AWSECSTaskFamily(taskMetadata.Family), semconv.AWSECSTaskRevision(taskMetadata.Revision), ) } return resource.NewWithAttributes(semconv.SchemaURL, attributes...), nil } func (detector *resourceDetector) getBaseArn(arns ...string) string { for _, arn := range arns { if i := strings.LastIndex(arn, ":"); i >= 0 { return arn[:i] } } return "" } func (detector *resourceDetector) getLogsAttributes(metadata *ecsmetadata.ContainerMetadataV4) ([]attribute.KeyValue, error) { if metadata.LogDriver != "awslogs" { return []attribute.KeyValue{}, nil } logsOptions := metadata.LogOptions if len(logsOptions.AwsLogsGroup) < 1 { return nil, errCannotRetrieveLogsGroupMetadataV4 } if len(logsOptions.AwsLogsStream) < 1 { return nil, errCannotRetrieveLogsStreamMetadataV4 } containerArn := metadata.ContainerARN // https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html const arnPartition = 1 const arnRegion = 3 const arnAccountId = 4 containerArnParts := strings.Split(containerArn, ":") // a valid arn should have at least 6 parts if len(containerArnParts) < 6 { return nil, errCannotRetrieveLogsStreamMetadataV4 } logsRegion := logsOptions.AwsRegion if len(logsRegion) < 1 { logsRegion = containerArnParts[arnRegion] } awsPartition := containerArnParts[arnPartition] awsAccount := containerArnParts[arnAccountId] awsLogGroupArn := strings.Join([]string{ "arn", awsPartition, "logs", logsRegion, awsAccount, "log-group", logsOptions.AwsLogsGroup, "*", }, ":") awsLogStreamArn := strings.Join([]string{ "arn", awsPartition, "logs", logsRegion, awsAccount, "log-group", logsOptions.AwsLogsGroup, "log-stream", logsOptions.AwsLogsStream, }, ":") return []attribute.KeyValue{ semconv.AWSLogGroupNames(logsOptions.AwsLogsGroup), semconv.AWSLogGroupARNs(awsLogGroupArn), semconv.AWSLogStreamNames(logsOptions.AwsLogsStream), semconv.AWSLogStreamARNs(awsLogStreamArn), }, nil } // returns metadata v4 for the container. func (ecsUtils ecsDetectorUtils) getContainerMetadataV4(ctx context.Context) (*ecsmetadata.ContainerMetadataV4, error) { return ecsmetadata.GetContainerV4(ctx, &http.Client{}) } // returns metadata v4 for the task. func (ecsUtils ecsDetectorUtils) getTaskMetadataV4(ctx context.Context) (*ecsmetadata.TaskMetadataV4, error) { return ecsmetadata.GetTaskV4(ctx, &http.Client{}) } // returns docker container ID from default c group path. func (ecsUtils ecsDetectorUtils) getContainerID() (string, error) { if runtime.GOOS != "linux" { // Cgroups are used only under Linux. return "", nil } fileData, err := os.ReadFile(defaultCgroupPath) if err != nil { // Cgroups file not found. // For example, windows; or when running integration tests outside of a container. return "", nil } splitData := strings.Split(strings.TrimSpace(string(fileData)), "\n") for _, str := range splitData { if len(str) > containerIDLength { return str[len(str)-containerIDLength:], nil } } return "", nil } // returns host name reported by the kernel. func (ecsUtils ecsDetectorUtils) getContainerName() (string, error) { hostName, err := os.Hostname() if err != nil { return "", errCannotReadContainerName } return hostName, nil } open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/aws/ecs/ecs_test.go000066400000000000000000000210351470323427300277520ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package ecs import ( "context" "fmt" "testing" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.26.0" metadata "github.com/brunoscheufler/aws-ecs-metadata-go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) // Create interface for functions that need to be mocked. type MockDetectorUtils struct { mock.Mock } func (detectorUtils *MockDetectorUtils) getContainerID() (string, error) { args := detectorUtils.Called() return args.String(0), args.Error(1) } func (detectorUtils *MockDetectorUtils) getContainerName() (string, error) { args := detectorUtils.Called() return args.String(0), args.Error(1) } func (detectorUtils *MockDetectorUtils) getContainerMetadataV4(_ context.Context) (*metadata.ContainerMetadataV4, error) { args := detectorUtils.Called() return args.Get(0).(*metadata.ContainerMetadataV4), args.Error(1) } func (detectorUtils *MockDetectorUtils) getTaskMetadataV4(_ context.Context) (*metadata.TaskMetadataV4, error) { args := detectorUtils.Called() return args.Get(0).(*metadata.TaskMetadataV4), args.Error(1) } // successfully returns resource when process is running on Amazon ECS environment // with no Metadata v4. func TestDetectV3(t *testing.T) { t.Setenv(metadataV3EnvVar, "3") detectorUtils := new(MockDetectorUtils) detectorUtils.On("getContainerName").Return("container-Name", nil) detectorUtils.On("getContainerID").Return("0123456789A", nil) detectorUtils.On("getContainerMetadataV4").Return(nil, fmt.Errorf("not supported")) detectorUtils.On("getTaskMetadataV4").Return(nil, fmt.Errorf("not supported")) attributes := []attribute.KeyValue{ semconv.CloudProviderAWS, semconv.CloudPlatformAWSECS, semconv.ContainerName("container-Name"), semconv.ContainerID("0123456789A"), } expectedResource := resource.NewWithAttributes(semconv.SchemaURL, attributes...) detector := &resourceDetector{utils: detectorUtils} res, _ := detector.Detect(context.Background()) assert.Equal(t, expectedResource, res, "Resource returned is incorrect") } // successfully returns resource when process is running on Amazon ECS environment // with Metadata v4. func TestDetectV4(t *testing.T) { t.Setenv(metadataV4EnvVar, "4") detectorUtils := new(MockDetectorUtils) detectorUtils.On("getContainerName").Return("container-Name", nil) detectorUtils.On("getContainerID").Return("0123456789A", nil) detectorUtils.On("getContainerMetadataV4").Return(&metadata.ContainerMetadataV4{ ContainerARN: "arn:aws:ecs:us-west-2:111122223333:container/05966557-f16c-49cb-9352-24b3a0dcd0e1", }, nil) detectorUtils.On("getTaskMetadataV4").Return(&metadata.TaskMetadataV4{ Cluster: "arn:aws:ecs:us-west-2:111122223333:cluster/default", TaskARN: "arn:aws:ecs:us-west-2:111122223333:task/default/e9028f8d5d8e4f258373e7b93ce9a3c3", Family: "curltest", Revision: "3", DesiredStatus: "RUNNING", KnownStatus: "RUNNING", Limits: metadata.Limits{ CPU: 0.25, Memory: 512, }, AvailabilityZone: "us-west-2a", LaunchType: "FARGATE", }, nil) attributes := []attribute.KeyValue{ semconv.CloudProviderAWS, semconv.CloudPlatformAWSECS, semconv.CloudAccountID("111122223333"), semconv.CloudRegion("us-west-2"), semconv.CloudAvailabilityZone("us-west-2a"), semconv.CloudResourceID("arn:aws:ecs:us-west-2:111122223333:container/05966557-f16c-49cb-9352-24b3a0dcd0e1"), semconv.ContainerName("container-Name"), semconv.ContainerID("0123456789A"), semconv.AWSECSClusterARN("arn:aws:ecs:us-west-2:111122223333:cluster/default"), semconv.AWSECSTaskARN("arn:aws:ecs:us-west-2:111122223333:task/default/e9028f8d5d8e4f258373e7b93ce9a3c3"), semconv.AWSECSLaunchtypeKey.String("fargate"), semconv.AWSECSTaskFamily("curltest"), semconv.AWSECSTaskRevision("3"), semconv.AWSECSContainerARN("arn:aws:ecs:us-west-2:111122223333:container/05966557-f16c-49cb-9352-24b3a0dcd0e1"), } expectedResource := resource.NewWithAttributes(semconv.SchemaURL, attributes...) detector := &resourceDetector{utils: detectorUtils} res, _ := detector.Detect(context.Background()) assert.Equal(t, expectedResource, res, "Resource returned is incorrect") } // returns empty resource when detector receives a bad task ARN from the Metadata v4 endpoint. func TestDetectBadARNsv4(t *testing.T) { t.Setenv(metadataV4EnvVar, "4") detectorUtils := new(MockDetectorUtils) detectorUtils.On("getContainerName").Return("container-Name", nil) detectorUtils.On("getContainerID").Return("0123456789A", nil) detectorUtils.On("getContainerMetadataV4").Return(&metadata.ContainerMetadataV4{ ContainerARN: "container/05966557-f16c-49cb-9352-24b3a0dcd0e1", }, nil) detectorUtils.On("getTaskMetadataV4").Return(&metadata.TaskMetadataV4{ Cluster: "default", TaskARN: "default/e9028f8d5d8e4f258373e7b93ce9a3c3", Family: "curltest", Revision: "3", DesiredStatus: "RUNNING", KnownStatus: "RUNNING", Limits: metadata.Limits{ CPU: 0.25, Memory: 512, }, AvailabilityZone: "us-west-2a", LaunchType: "FARGATE", }, nil) detector := &resourceDetector{utils: detectorUtils} _, err := detector.Detect(context.Background()) assert.Equal(t, errCannotParseTaskArn, err) } // returns empty resource when detector cannot read container ID. func TestDetectCannotReadContainerID(t *testing.T) { t.Setenv(metadataV3EnvVar, "3") detectorUtils := new(MockDetectorUtils) detectorUtils.On("getContainerName").Return("container-Name", nil) detectorUtils.On("getContainerID").Return("", nil) detectorUtils.On("getContainerMetadataV4").Return(nil, fmt.Errorf("not supported")) detectorUtils.On("getTaskMetadataV4").Return(nil, fmt.Errorf("not supported")) attributes := []attribute.KeyValue{ semconv.CloudProviderAWS, semconv.CloudPlatformAWSECS, semconv.ContainerName("container-Name"), semconv.ContainerID(""), } expectedResource := resource.NewWithAttributes(semconv.SchemaURL, attributes...) detector := &resourceDetector{utils: detectorUtils} res, err := detector.Detect(context.Background()) assert.NoError(t, err) assert.Equal(t, expectedResource, res, "Resource returned is incorrect") } // returns empty resource when detector cannot read container Name. func TestDetectCannotReadContainerName(t *testing.T) { t.Setenv(metadataV3EnvVar, "3") detectorUtils := new(MockDetectorUtils) detectorUtils.On("getContainerName").Return("", errCannotReadContainerName) detectorUtils.On("getContainerID").Return("0123456789A", nil) detectorUtils.On("getContainerMetadataV4").Return(nil, fmt.Errorf("not supported")) detectorUtils.On("getTaskMetadataV4").Return(nil, fmt.Errorf("not supported")) detector := &resourceDetector{utils: detectorUtils} res, err := detector.Detect(context.Background()) assert.Equal(t, errCannotReadContainerName, err) assert.Empty(t, res.Attributes()) } // returns empty resource when process is not running ECS. func TestReturnsIfNoEnvVars(t *testing.T) { detector := &resourceDetector{utils: nil} res, err := detector.Detect(context.Background()) // When not on ECS, the detector should return nil and not error. assert.NoError(t, err, "failure to detect when not on platform must not be an error") assert.Nil(t, res, "failure to detect should return a nil Resource to optimize merge") } // handles alternative aws partitions (e.g. AWS GovCloud). func TestLogsAttributesAlternatePartition(t *testing.T) { detector := &resourceDetector{utils: nil} containerMetadata := &metadata.ContainerMetadataV4{ LogDriver: "awslogs", LogOptions: struct { AwsLogsCreateGroup string `json:"awslogs-create-group"` AwsLogsGroup string `json:"awslogs-group"` AwsLogsStream string `json:"awslogs-stream"` AwsRegion string `json:"awslogs-region"` }{ "fake-create", "fake-group", "fake-stream", "", }, ContainerARN: "arn:arn-partition:arn-svc:arn-region:arn-account:arn-resource", } actualAttributes, err := detector.getLogsAttributes(containerMetadata) assert.NoError(t, err, "failure with nonstandard partition") expectedAttributes := []attribute.KeyValue{ semconv.AWSLogGroupNames(containerMetadata.LogOptions.AwsLogsGroup), semconv.AWSLogGroupARNs("arn:arn-partition:logs:arn-region:arn-account:log-group:fake-group:*"), semconv.AWSLogStreamNames(containerMetadata.LogOptions.AwsLogsStream), semconv.AWSLogStreamARNs("arn:arn-partition:logs:arn-region:arn-account:log-group:fake-group:log-stream:fake-stream"), } assert.Equal(t, expectedAttributes, actualAttributes, "logs attributes are incorrect") } open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/aws/ecs/go.mod000066400000000000000000000013331470323427300267170ustar00rootroot00000000000000module go.opentelemetry.io/contrib/detectors/aws/ecs go 1.22 require ( github.com/brunoscheufler/aws-ecs-metadata-go v0.0.0-20221221133751-67e37ae746cd github.com/stretchr/testify v1.9.0 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/sdk v1.31.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/stretchr/objx v0.5.2 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect go.opentelemetry.io/otel/trace v1.31.0 // indirect golang.org/x/sys v0.26.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/aws/ecs/go.sum000066400000000000000000000066201470323427300267500ustar00rootroot00000000000000github.com/brunoscheufler/aws-ecs-metadata-go v0.0.0-20221221133751-67e37ae746cd h1:C0dfBzAdNMqxokqWUysk2KTJSMmqvh9cNW1opdy5+0Q= github.com/brunoscheufler/aws-ecs-metadata-go v0.0.0-20221221133751-67e37ae746cd/go.mod h1:CeKhh8xSs3WZAc50xABMxu+FlfAAd5PNumo7NfOv7EE= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/aws/ecs/test/000077500000000000000000000000001470323427300265705ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/aws/ecs/test/ecs_test.go000066400000000000000000000242351470323427300307360ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package ecs import ( "context" "net/http" "net/http/httptest" "os" "strings" "testing" ecs "go.opentelemetry.io/contrib/detectors/aws/ecs" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.26.0" "github.com/stretchr/testify/assert" ) const ( metadataV4EnvVar = "ECS_CONTAINER_METADATA_URI_V4" ) // successfully returns resource when process is running on Amazon ECS environment // with Metadata v4 with the EC2 Launch type. func TestDetectV4LaunchTypeEc2(t *testing.T) { testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { if strings.HasSuffix(req.URL.String(), "/task") { content, err := os.ReadFile("metadatav4-response-task-ec2.json") if err == nil { _, err = res.Write(content) if err != nil { t.Fatal(err) } } } else { content, err := os.ReadFile("metadatav4-response-container-ec2.json") if err == nil { _, err = res.Write(content) if err != nil { t.Fatal(err) } } } })) defer testServer.Close() t.Setenv(metadataV4EnvVar, testServer.URL) hostname, err := os.Hostname() assert.NoError(t, err, "Error") attributes := []attribute.KeyValue{ semconv.CloudProviderAWS, semconv.CloudPlatformAWSECS, semconv.CloudAccountID("111122223333"), semconv.CloudRegion("us-west-2"), semconv.CloudAvailabilityZone("us-west-2d"), semconv.CloudResourceID("arn:aws:ecs:us-west-2:111122223333:container/0206b271-b33f-47ab-86c6-a0ba208a70a9"), semconv.ContainerName(hostname), // We are not running the test in an actual container, // the container id is tested with mocks of the cgroup // file in the unit tests semconv.ContainerID(""), semconv.AWSECSContainerARN("arn:aws:ecs:us-west-2:111122223333:container/0206b271-b33f-47ab-86c6-a0ba208a70a9"), semconv.AWSECSClusterARN("arn:aws:ecs:us-west-2:111122223333:cluster/default"), semconv.AWSECSLaunchtypeKey.String("ec2"), semconv.AWSECSTaskARN("arn:aws:ecs:us-west-2:111122223333:task/default/158d1c8083dd49d6b527399fd6414f5c"), semconv.AWSECSTaskFamily("curltest"), semconv.AWSECSTaskRevision("26"), semconv.AWSLogGroupNames("/ecs/metadata"), semconv.AWSLogGroupARNs("arn:aws:logs:us-west-2:111122223333:log-group:/ecs/metadata:*"), semconv.AWSLogStreamNames("ecs/curl/8f03e41243824aea923aca126495f665"), semconv.AWSLogStreamARNs("arn:aws:logs:us-west-2:111122223333:log-group:/ecs/metadata:log-stream:ecs/curl/8f03e41243824aea923aca126495f665"), } expectedResource := resource.NewWithAttributes(semconv.SchemaURL, attributes...) detector := ecs.NewResourceDetector() res, err := detector.Detect(context.Background()) assert.NoError(t, err, "Detector should not fail") assert.Equal(t, expectedResource, res, "Resource returned is incorrect") } // successfully returns resource when process is running on Amazon ECS environment // with Metadata v4 with the EC2 Launch type and bad ContainerARN. func TestDetectV4LaunchTypeEc2BadContainerArn(t *testing.T) { testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { if strings.HasSuffix(req.URL.String(), "/task") { content, err := os.ReadFile("metadatav4-response-task-ec2.json") if err == nil { _, err = res.Write(content) if err != nil { t.Fatal(err) } } } else { content, err := os.ReadFile("metadatav4-response-container-ec2-bad-container-arn.json") if err == nil { _, err = res.Write(content) if err != nil { t.Fatal(err) } } } })) defer testServer.Close() t.Setenv(metadataV4EnvVar, testServer.URL) hostname, err := os.Hostname() assert.NoError(t, err, "Error") attributes := []attribute.KeyValue{ semconv.CloudProviderAWS, semconv.CloudPlatformAWSECS, semconv.CloudAccountID("111122223333"), semconv.CloudRegion("us-west-2"), semconv.CloudAvailabilityZone("us-west-2d"), semconv.CloudResourceID("arn:aws:ecs:us-west-2:111122223333:container/0206b271-b33f-47ab-86c6-a0ba208a70a9"), semconv.ContainerName(hostname), // We are not running the test in an actual container, // the container id is tested with mocks of the cgroup // file in the unit tests semconv.ContainerID(""), semconv.AWSECSContainerARN("arn:aws:ecs:us-west-2:111122223333:container/0206b271-b33f-47ab-86c6-a0ba208a70a9"), semconv.AWSECSClusterARN("arn:aws:ecs:us-west-2:111122223333:cluster/default"), semconv.AWSECSLaunchtypeKey.String("ec2"), semconv.AWSECSTaskARN("arn:aws:ecs:us-west-2:111122223333:task/default/158d1c8083dd49d6b527399fd6414f5c"), semconv.AWSECSTaskFamily("curltest"), semconv.AWSECSTaskRevision("26"), semconv.AWSLogGroupNames("/ecs/metadata"), semconv.AWSLogGroupARNs("arn:aws:logs:us-west-2:111122223333:log-group:/ecs/metadata:*"), semconv.AWSLogStreamNames("ecs/curl/8f03e41243824aea923aca126495f665"), semconv.AWSLogStreamARNs("arn:aws:logs:us-west-2:111122223333:log-group:/ecs/metadata:log-stream:ecs/curl/8f03e41243824aea923aca126495f665"), } expectedResource := resource.NewWithAttributes(semconv.SchemaURL, attributes...) detector := ecs.NewResourceDetector() res, err := detector.Detect(context.Background()) assert.NoError(t, err, "Detector should not fail") assert.Equal(t, expectedResource, res, "Resource returned is incorrect") } // successfully returns resource when process is running on Amazon ECS environment // with Metadata v4 with the EC2 Launch type and bad TaskARN. func TestDetectV4LaunchTypeEc2BadTaskArn(t *testing.T) { testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { if strings.HasSuffix(req.URL.String(), "/task") { content, err := os.ReadFile("metadatav4-response-task-ec2-bad-task-arn.json") if err == nil { _, err = res.Write(content) if err != nil { t.Fatal(err) } } } else { content, err := os.ReadFile("metadatav4-response-container-ec2.json") if err == nil { _, err = res.Write(content) if err != nil { t.Fatal(err) } } } })) defer testServer.Close() t.Setenv(metadataV4EnvVar, testServer.URL) hostname, err := os.Hostname() assert.NoError(t, err, "Error") attributes := []attribute.KeyValue{ semconv.CloudProviderAWS, semconv.CloudPlatformAWSECS, semconv.ContainerName(hostname), semconv.CloudAccountID("111122223333"), semconv.CloudRegion("us-west-2"), semconv.CloudAvailabilityZone("us-west-2d"), semconv.CloudResourceID("arn:aws:ecs:us-west-2:111122223333:container/0206b271-b33f-47ab-86c6-a0ba208a70a9"), // We are not running the test in an actual container, // the container id is tested with mocks of the cgroup // file in the unit tests semconv.ContainerID(""), semconv.AWSECSContainerARN("arn:aws:ecs:us-west-2:111122223333:container/0206b271-b33f-47ab-86c6-a0ba208a70a9"), semconv.AWSECSClusterARN("arn:aws:ecs:us-west-2:111122223333:cluster/default"), semconv.AWSECSLaunchtypeKey.String("ec2"), semconv.AWSECSTaskARN("arn:aws:ecs:us-west-2:111122223333:task/default/158d1c8083dd49d6b527399fd6414f5c"), semconv.AWSECSTaskFamily("curltest"), semconv.AWSECSTaskRevision("26"), semconv.AWSLogGroupNames("/ecs/metadata"), semconv.AWSLogGroupARNs("arn:aws:logs:us-west-2:111122223333:log-group:/ecs/metadata:*"), semconv.AWSLogStreamNames("ecs/curl/8f03e41243824aea923aca126495f665"), semconv.AWSLogStreamARNs("arn:aws:logs:us-west-2:111122223333:log-group:/ecs/metadata:log-stream:ecs/curl/8f03e41243824aea923aca126495f665"), } expectedResource := resource.NewWithAttributes(semconv.SchemaURL, attributes...) detector := ecs.NewResourceDetector() res, err := detector.Detect(context.Background()) assert.NoError(t, err, "Detector should not fail") assert.Equal(t, expectedResource, res, "Resource returned is incorrect") } // successfully returns resource when process is running on Amazon ECS environment // with Metadata v4 with the Fargate Launch type. func TestDetectV4LaunchTypeFargate(t *testing.T) { testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { if strings.HasSuffix(req.URL.String(), "/task") { content, err := os.ReadFile("metadatav4-response-task-fargate.json") if err == nil { _, err = res.Write(content) if err != nil { panic(err) } } } else { content, err := os.ReadFile("metadatav4-response-container-fargate.json") if err == nil { _, err = res.Write(content) if err != nil { panic(err) } } } })) defer testServer.Close() t.Setenv(metadataV4EnvVar, testServer.URL) hostname, err := os.Hostname() assert.NoError(t, err, "Error") attributes := []attribute.KeyValue{ semconv.CloudProviderAWS, semconv.CloudPlatformAWSECS, semconv.ContainerName(hostname), semconv.CloudAccountID("111122223333"), semconv.CloudRegion("us-west-2"), semconv.CloudAvailabilityZone("us-west-2a"), semconv.CloudResourceID("arn:aws:ecs:us-west-2:111122223333:container/05966557-f16c-49cb-9352-24b3a0dcd0e1"), // We are not running the test in an actual container, // the container id is tested with mocks of the cgroup // file in the unit tests semconv.ContainerID(""), semconv.AWSECSContainerARN("arn:aws:ecs:us-west-2:111122223333:container/05966557-f16c-49cb-9352-24b3a0dcd0e1"), semconv.AWSECSClusterARN("arn:aws:ecs:us-west-2:111122223333:cluster/default"), semconv.AWSECSLaunchtypeKey.String("fargate"), semconv.AWSECSTaskARN("arn:aws:ecs:us-west-2:111122223333:task/default/e9028f8d5d8e4f258373e7b93ce9a3c3"), semconv.AWSECSTaskFamily("curltest"), semconv.AWSECSTaskRevision("3"), semconv.AWSLogGroupNames("/ecs/containerlogs"), semconv.AWSLogGroupARNs("arn:aws:logs:us-west-2:111122223333:log-group:/ecs/containerlogs:*"), semconv.AWSLogStreamNames("ecs/curl/cd189a933e5849daa93386466019ab50"), semconv.AWSLogStreamARNs("arn:aws:logs:us-west-2:111122223333:log-group:/ecs/containerlogs:log-stream:ecs/curl/cd189a933e5849daa93386466019ab50"), } expectedResource := resource.NewWithAttributes(semconv.SchemaURL, attributes...) detector := ecs.NewResourceDetector() res, err := detector.Detect(context.Background()) assert.NoError(t, err, "Detector should not fail") assert.Equal(t, expectedResource, res, "Resource returned is incorrect") } metadatav4-response-container-ec2-bad-container-arn.json000066400000000000000000000032141470323427300412030ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/aws/ecs/test{ "DockerId": "ea32192c8553fbff06c9340478a2ff089b2bb5646fb718b4ee206641c9086d66", "Name": "curl", "DockerName": "ecs-curltest-24-curl-cca48e8dcadd97805600", "Image": "111122223333.dkr.ecr.us-west-2.amazonaws.com/curltest:latest", "ImageID": "sha256:d691691e9652791a60114e67b365688d20d19940dde7c4736ea30e660d8d3553", "Labels": { "com.amazonaws.ecs.cluster": "default", "com.amazonaws.ecs.container-name": "curl", "com.amazonaws.ecs.task-arn": "arn:aws:ecs:us-west-2:111122223333:task/default/8f03e41243824aea923aca126495f665", "com.amazonaws.ecs.task-definition-family": "curltest", "com.amazonaws.ecs.task-definition-version": "24" }, "DesiredStatus": "RUNNING", "KnownStatus": "RUNNING", "Limits": { "CPU": 10, "Memory": 128 }, "CreatedAt": "2020-10-02T00:15:07.620912337Z", "StartedAt": "2020-10-02T00:15:08.062559351Z", "Type": "NORMAL", "LogDriver": "awslogs", "LogOptions": { "awslogs-create-group": "true", "awslogs-group": "/ecs/metadata", "awslogs-region": "us-west-2", "awslogs-stream": "ecs/curl/8f03e41243824aea923aca126495f665" }, "ContainerARN": "0206b271-b33f-47ab-86c6-a0ba208a70a9", "Networks": [ { "NetworkMode": "awsvpc", "IPv4Addresses": [ "10.0.2.100" ], "AttachmentIndex": 0, "MACAddress": "0e:9e:32:c7:48:85", "IPv4SubnetCIDRBlock": "10.0.2.0/24", "PrivateDNSName": "ip-10-0-2-100.us-west-2.compute.internal", "SubnetGatewayIpv4Address": "10.0.2.1/24" } ] } metadatav4-response-container-ec2.json000066400000000000000000000032711470323427300357240ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/aws/ecs/test{ "DockerId": "ea32192c8553fbff06c9340478a2ff089b2bb5646fb718b4ee206641c9086d66", "Name": "curl", "DockerName": "ecs-curltest-24-curl-cca48e8dcadd97805600", "Image": "111122223333.dkr.ecr.us-west-2.amazonaws.com/curltest:latest", "ImageID": "sha256:d691691e9652791a60114e67b365688d20d19940dde7c4736ea30e660d8d3553", "Labels": { "com.amazonaws.ecs.cluster": "default", "com.amazonaws.ecs.container-name": "curl", "com.amazonaws.ecs.task-arn": "arn:aws:ecs:us-west-2:111122223333:task/default/8f03e41243824aea923aca126495f665", "com.amazonaws.ecs.task-definition-family": "curltest", "com.amazonaws.ecs.task-definition-version": "24" }, "DesiredStatus": "RUNNING", "KnownStatus": "RUNNING", "Limits": { "CPU": 10, "Memory": 128 }, "CreatedAt": "2020-10-02T00:15:07.620912337Z", "StartedAt": "2020-10-02T00:15:08.062559351Z", "Type": "NORMAL", "LogDriver": "awslogs", "LogOptions": { "awslogs-create-group": "true", "awslogs-group": "/ecs/metadata", "awslogs-region": "us-west-2", "awslogs-stream": "ecs/curl/8f03e41243824aea923aca126495f665" }, "ContainerARN": "arn:aws:ecs:us-west-2:111122223333:container/0206b271-b33f-47ab-86c6-a0ba208a70a9", "Networks": [ { "NetworkMode": "awsvpc", "IPv4Addresses": [ "10.0.2.100" ], "AttachmentIndex": 0, "MACAddress": "0e:9e:32:c7:48:85", "IPv4SubnetCIDRBlock": "10.0.2.0/24", "PrivateDNSName": "ip-10-0-2-100.us-west-2.compute.internal", "SubnetGatewayIpv4Address": "10.0.2.1/24" } ] } metadatav4-response-container-fargate.json000066400000000000000000000035341470323427300366660ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/aws/ecs/test{ "DockerId": "cd189a933e5849daa93386466019ab50-2495160603", "Name": "curl", "DockerName": "curl", "Image": "111122223333.dkr.ecr.us-west-2.amazonaws.com/curltest:latest", "ImageID": "sha256:25f3695bedfb454a50f12d127839a68ad3caf91e451c1da073db34c542c4d2cb", "Labels": { "com.amazonaws.ecs.cluster": "arn:aws:ecs:us-west-2:111122223333:cluster/default", "com.amazonaws.ecs.container-name": "curl", "com.amazonaws.ecs.task-arn": "arn:aws:ecs:us-west-2:111122223333:task/default/cd189a933e5849daa93386466019ab50", "com.amazonaws.ecs.task-definition-family": "curltest", "com.amazonaws.ecs.task-definition-version": "2" }, "DesiredStatus": "RUNNING", "KnownStatus": "RUNNING", "Limits": { "CPU": 10, "Memory": 128 }, "CreatedAt": "2020-10-08T20:09:11.44527186Z", "StartedAt": "2020-10-08T20:09:11.44527186Z", "Type": "NORMAL", "Networks": [ { "NetworkMode": "awsvpc", "IPv4Addresses": [ "192.0.2.3" ], "AttachmentIndex": 0, "MACAddress": "0a:de:f6:10:51:e5", "IPv4SubnetCIDRBlock": "192.0.2.0/24", "DomainNameServers": [ "192.0.2.2" ], "DomainNameSearchList": [ "us-west-2.compute.internal" ], "PrivateDNSName": "ip-10-0-0-222.us-west-2.compute.internal", "SubnetGatewayIpv4Address": "192.0.2.0/24" } ], "ContainerARN": "arn:aws:ecs:us-west-2:111122223333:container/05966557-f16c-49cb-9352-24b3a0dcd0e1", "LogOptions": { "awslogs-create-group": "true", "awslogs-group": "/ecs/containerlogs", "awslogs-region": "us-west-2", "awslogs-stream": "ecs/curl/cd189a933e5849daa93386466019ab50" }, "LogDriver": "awslogs" }metadatav4-response-task-ec2-bad-task-arn.json000066400000000000000000000077751470323427300371630ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/aws/ecs/test{ "Cluster": "default", "TaskARN": "default/158d1c8083dd49d6b527399fd6414f5c", "Family": "curltest", "Revision": "26", "DesiredStatus": "RUNNING", "KnownStatus": "RUNNING", "PullStartedAt": "2020-10-02T00:43:06.202617438Z", "PullStoppedAt": "2020-10-02T00:43:06.31288465Z", "AvailabilityZone": "us-west-2d", "LaunchType": "EC2", "Containers": [ { "DockerId": "598cba581fe3f939459eaba1e071d5c93bb2c49b7d1ba7db6bb19deeb70d8e38", "Name": "~internal~ecs~pause", "DockerName": "ecs-curltest-26-internalecspause-e292d586b6f9dade4a00", "Image": "amazon/amazon-ecs-pause:0.1.0", "ImageID": "", "Labels": { "com.amazonaws.ecs.cluster": "default", "com.amazonaws.ecs.container-name": "~internal~ecs~pause", "com.amazonaws.ecs.task-arn": "arn:aws:ecs:us-west-2:111122223333:task/default/158d1c8083dd49d6b527399fd6414f5c", "com.amazonaws.ecs.task-definition-family": "curltest", "com.amazonaws.ecs.task-definition-version": "26" }, "DesiredStatus": "RESOURCES_PROVISIONED", "KnownStatus": "RESOURCES_PROVISIONED", "Limits": { "CPU": 0, "Memory": 0 }, "CreatedAt": "2020-10-02T00:43:05.602352471Z", "StartedAt": "2020-10-02T00:43:06.076707576Z", "Type": "CNI_PAUSE", "Networks": [ { "NetworkMode": "awsvpc", "IPv4Addresses": [ "10.0.2.61" ], "AttachmentIndex": 0, "MACAddress": "0e:10:e2:01:bd:91", "IPv4SubnetCIDRBlock": "10.0.2.0/24", "PrivateDNSName": "ip-10-0-2-61.us-west-2.compute.internal", "SubnetGatewayIpv4Address": "10.0.2.1/24" } ] }, { "DockerId": "ee08638adaaf009d78c248913f629e38299471d45fe7dc944d1039077e3424ca", "Name": "curl", "DockerName": "ecs-curltest-26-curl-a0e7dba5aca6d8cb2e00", "Image": "111122223333.dkr.ecr.us-west-2.amazonaws.com/curltest:latest", "ImageID": "sha256:d691691e9652791a60114e67b365688d20d19940dde7c4736ea30e660d8d3553", "Labels": { "com.amazonaws.ecs.cluster": "default", "com.amazonaws.ecs.container-name": "curl", "com.amazonaws.ecs.task-arn": "arn:aws:ecs:us-west-2:111122223333:task/default/158d1c8083dd49d6b527399fd6414f5c", "com.amazonaws.ecs.task-definition-family": "curltest", "com.amazonaws.ecs.task-definition-version": "26" }, "DesiredStatus": "RUNNING", "KnownStatus": "RUNNING", "Limits": { "CPU": 10, "Memory": 128 }, "CreatedAt": "2020-10-02T00:43:06.326590752Z", "StartedAt": "2020-10-02T00:43:06.767535449Z", "Type": "NORMAL", "LogDriver": "awslogs", "LogOptions": { "awslogs-create-group": "true", "awslogs-group": "/ecs/metadata", "awslogs-region": "us-west-2", "awslogs-stream": "ecs/curl/158d1c8083dd49d6b527399fd6414f5c" }, "ContainerARN": "arn:aws:ecs:us-west-2:111122223333:container/abb51bdd-11b4-467f-8f6c-adcfe1fe059d", "Networks": [ { "NetworkMode": "awsvpc", "IPv4Addresses": [ "10.0.2.61" ], "AttachmentIndex": 0, "MACAddress": "0e:10:e2:01:bd:91", "IPv4SubnetCIDRBlock": "10.0.2.0/24", "PrivateDNSName": "ip-10-0-2-61.us-west-2.compute.internal", "SubnetGatewayIpv4Address": "10.0.2.1/24" } ] } ] } metadatav4-response-task-ec2.json000066400000000000000000000100451470323427300347010ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/aws/ecs/test{ "Cluster": "default", "TaskARN": "arn:aws:ecs:us-west-2:111122223333:task/default/158d1c8083dd49d6b527399fd6414f5c", "Family": "curltest", "Revision": "26", "DesiredStatus": "RUNNING", "KnownStatus": "RUNNING", "PullStartedAt": "2020-10-02T00:43:06.202617438Z", "PullStoppedAt": "2020-10-02T00:43:06.31288465Z", "AvailabilityZone": "us-west-2d", "LaunchType": "EC2", "Containers": [ { "DockerId": "598cba581fe3f939459eaba1e071d5c93bb2c49b7d1ba7db6bb19deeb70d8e38", "Name": "~internal~ecs~pause", "DockerName": "ecs-curltest-26-internalecspause-e292d586b6f9dade4a00", "Image": "amazon/amazon-ecs-pause:0.1.0", "ImageID": "", "Labels": { "com.amazonaws.ecs.cluster": "default", "com.amazonaws.ecs.container-name": "~internal~ecs~pause", "com.amazonaws.ecs.task-arn": "arn:aws:ecs:us-west-2:111122223333:task/default/158d1c8083dd49d6b527399fd6414f5c", "com.amazonaws.ecs.task-definition-family": "curltest", "com.amazonaws.ecs.task-definition-version": "26" }, "DesiredStatus": "RESOURCES_PROVISIONED", "KnownStatus": "RESOURCES_PROVISIONED", "Limits": { "CPU": 0, "Memory": 0 }, "CreatedAt": "2020-10-02T00:43:05.602352471Z", "StartedAt": "2020-10-02T00:43:06.076707576Z", "Type": "CNI_PAUSE", "Networks": [ { "NetworkMode": "awsvpc", "IPv4Addresses": [ "10.0.2.61" ], "AttachmentIndex": 0, "MACAddress": "0e:10:e2:01:bd:91", "IPv4SubnetCIDRBlock": "10.0.2.0/24", "PrivateDNSName": "ip-10-0-2-61.us-west-2.compute.internal", "SubnetGatewayIpv4Address": "10.0.2.1/24" } ] }, { "DockerId": "ee08638adaaf009d78c248913f629e38299471d45fe7dc944d1039077e3424ca", "Name": "curl", "DockerName": "ecs-curltest-26-curl-a0e7dba5aca6d8cb2e00", "Image": "111122223333.dkr.ecr.us-west-2.amazonaws.com/curltest:latest", "ImageID": "sha256:d691691e9652791a60114e67b365688d20d19940dde7c4736ea30e660d8d3553", "Labels": { "com.amazonaws.ecs.cluster": "default", "com.amazonaws.ecs.container-name": "curl", "com.amazonaws.ecs.task-arn": "arn:aws:ecs:us-west-2:111122223333:task/default/158d1c8083dd49d6b527399fd6414f5c", "com.amazonaws.ecs.task-definition-family": "curltest", "com.amazonaws.ecs.task-definition-version": "26" }, "DesiredStatus": "RUNNING", "KnownStatus": "RUNNING", "Limits": { "CPU": 10, "Memory": 128 }, "CreatedAt": "2020-10-02T00:43:06.326590752Z", "StartedAt": "2020-10-02T00:43:06.767535449Z", "Type": "NORMAL", "LogDriver": "awslogs", "LogOptions": { "awslogs-create-group": "true", "awslogs-group": "/ecs/metadata", "awslogs-region": "us-west-2", "awslogs-stream": "ecs/curl/158d1c8083dd49d6b527399fd6414f5c" }, "ContainerARN": "arn:aws:ecs:us-west-2:111122223333:container/abb51bdd-11b4-467f-8f6c-adcfe1fe059d", "Networks": [ { "NetworkMode": "awsvpc", "IPv4Addresses": [ "10.0.2.61" ], "AttachmentIndex": 0, "MACAddress": "0e:10:e2:01:bd:91", "IPv4SubnetCIDRBlock": "10.0.2.0/24", "PrivateDNSName": "ip-10-0-2-61.us-west-2.compute.internal", "SubnetGatewayIpv4Address": "10.0.2.1/24" } ] } ] } metadatav4-response-task-fargate.json000066400000000000000000000062711470323427300356470ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/aws/ecs/test{ "Cluster": "arn:aws:ecs:us-west-2:111122223333:cluster/default", "TaskARN": "arn:aws:ecs:us-west-2:111122223333:task/default/e9028f8d5d8e4f258373e7b93ce9a3c3", "Family": "curltest", "Revision": "3", "DesiredStatus": "RUNNING", "KnownStatus": "RUNNING", "Limits": { "CPU": 0.25, "Memory": 512 }, "PullStartedAt": "2020-10-08T20:47:16.053330955Z", "PullStoppedAt": "2020-10-08T20:47:19.592684631Z", "AvailabilityZone": "us-west-2a", "Containers": [ { "DockerId": "e9028f8d5d8e4f258373e7b93ce9a3c3-2495160603", "Name": "curl", "DockerName": "curl", "Image": "111122223333.dkr.ecr.us-west-2.amazonaws.com/curltest:latest", "ImageID": "sha256:25f3695bedfb454a50f12d127839a68ad3caf91e451c1da073db34c542c4d2cb", "Labels": { "com.amazonaws.ecs.cluster": "arn:aws:ecs:us-west-2:111122223333:cluster/default", "com.amazonaws.ecs.container-name": "curl", "com.amazonaws.ecs.task-arn": "arn:aws:ecs:us-west-2:111122223333:task/default/e9028f8d5d8e4f258373e7b93ce9a3c3", "com.amazonaws.ecs.task-definition-family": "curltest", "com.amazonaws.ecs.task-definition-version": "3" }, "DesiredStatus": "RUNNING", "KnownStatus": "RUNNING", "Limits": { "CPU": 10, "Memory": 128 }, "CreatedAt": "2020-10-08T20:47:20.567813946Z", "StartedAt": "2020-10-08T20:47:20.567813946Z", "Type": "NORMAL", "Networks": [ { "NetworkMode": "awsvpc", "IPv4Addresses": [ "192.0.2.3" ], "IPv6Addresses": [ "2001:dB8:10b:1a00:32bf:a372:d80f:e958" ], "AttachmentIndex": 0, "MACAddress": "02:b7:20:19:72:39", "IPv4SubnetCIDRBlock": "192.0.2.0/24", "IPv6SubnetCIDRBlock": "2600:1f13:10b:1a00::/64", "DomainNameServers": [ "192.0.2.2" ], "DomainNameSearchList": [ "us-west-2.compute.internal" ], "PrivateDNSName": "ip-172-31-30-173.us-west-2.compute.internal", "SubnetGatewayIpv4Address": "192.0.2.0/24" } ], "ClockDrift": { "ClockErrorBound": 0.5458234999999999, "ReferenceTimestamp": "2021-09-07T16:57:44Z", "ClockSynchronizationStatus": "SYNCHRONIZED" }, "ContainerARN": "arn:aws:ecs:us-west-2:111122223333:container/1bdcca8b-f905-4ee6-885c-4064cb70f6e6", "LogOptions": { "awslogs-create-group": "true", "awslogs-group": "/ecs/containerlogs", "awslogs-region": "us-west-2", "awslogs-stream": "ecs/curl/e9028f8d5d8e4f258373e7b93ce9a3c3" }, "LogDriver": "awslogs" } ], "LaunchType": "FARGATE" }open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/aws/ecs/version.go000066400000000000000000000007701470323427300276310ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package ecs // import "go.opentelemetry.io/contrib/detectors/aws/ecs" // Version is the current release version of the ECS resource detector. func Version() string { return "1.31.0" // This string is updated by the pre_release.sh script during release } // SemVersion is the semantic version to be supplied to tracer/meter creation. // // Deprecated: Use [Version] instead. func SemVersion() string { return Version() } open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/aws/eks/000077500000000000000000000000001470323427300256215ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/aws/eks/detector.go000066400000000000000000000137061470323427300277700ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package eks // import "go.opentelemetry.io/contrib/detectors/aws/eks" import ( "context" "fmt" "os" "regexp" "strings" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.26.0" ) const ( k8sTokenPath = "/var/run/secrets/kubernetes.io/serviceaccount/token" //nolint:gosec // False positive G101: Potential hardcoded credentials. The detector only check if the token exists. k8sCertPath = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt" authConfigmapNS = "kube-system" authConfigmapName = "aws-auth" cwConfigmapNS = "amazon-cloudwatch" cwConfigmapName = "cluster-info" defaultCgroupPath = "/proc/self/cgroup" containerIDLength = 64 ) // detectorUtils is used for testing the resourceDetector by abstracting functions that rely on external systems. type detectorUtils interface { fileExists(filename string) bool getConfigMap(ctx context.Context, namespace string, name string) (map[string]string, error) getContainerID() (string, error) } // This struct will implement the detectorUtils interface. type eksDetectorUtils struct { clientset *kubernetes.Clientset } // resourceDetector for detecting resources running on Amazon EKS. type resourceDetector struct { utils detectorUtils err error } // Compile time assertion that resourceDetector implements the resource.Detector interface. var _ resource.Detector = (*resourceDetector)(nil) // Compile time assertion that eksDetectorUtils implements the detectorUtils interface. var _ detectorUtils = (*eksDetectorUtils)(nil) // NewResourceDetector returns a resource detector that will detect AWS EKS resources. func NewResourceDetector() resource.Detector { utils, err := newK8sDetectorUtils() return &resourceDetector{utils: utils, err: err} } // Detect returns a Resource describing the Amazon EKS environment being run in. func (detector *resourceDetector) Detect(ctx context.Context) (*resource.Resource, error) { if detector.err != nil { return nil, detector.err } isEks, err := isEKS(ctx, detector.utils) if err != nil { return nil, err } // Return empty resource object if not running in EKS if !isEks { return resource.Empty(), nil } // Create variable to hold resource attributes attributes := []attribute.KeyValue{ semconv.CloudProviderAWS, semconv.CloudPlatformAWSEKS, } // Get clusterName and append to attributes clusterName, err := getClusterName(ctx, detector.utils) if err != nil { return nil, err } if clusterName != "" { attributes = append(attributes, semconv.K8SClusterName(clusterName)) } // Get containerID and append to attributes containerID, err := detector.utils.getContainerID() if err != nil { return nil, err } if containerID != "" { attributes = append(attributes, semconv.ContainerID(containerID)) } // Return new resource object with clusterName and containerID as attributes return resource.NewWithAttributes(semconv.SchemaURL, attributes...), nil } // isEKS checks if the current environment is running in EKS. func isEKS(ctx context.Context, utils detectorUtils) (bool, error) { if !isK8s(utils) { return false, nil } // Make HTTP GET request awsAuth, err := utils.getConfigMap(ctx, authConfigmapNS, authConfigmapName) if err != nil { return false, fmt.Errorf("isEks() error retrieving auth configmap: %w", err) } return awsAuth != nil, nil } // newK8sDetectorUtils creates the Kubernetes clientset. func newK8sDetectorUtils() (*eksDetectorUtils, error) { // Get cluster configuration confs, err := rest.InClusterConfig() if err != nil { return nil, fmt.Errorf("failed to create config: %w", err) } // Create clientset using generated configuration clientset, err := kubernetes.NewForConfig(confs) if err != nil { return nil, fmt.Errorf("failed to create clientset for Kubernetes client") } return &eksDetectorUtils{clientset: clientset}, nil } // isK8s checks if the current environment is running in a Kubernetes environment. func isK8s(utils detectorUtils) bool { return utils.fileExists(k8sTokenPath) && utils.fileExists(k8sCertPath) } // fileExists checks if a file with a given filename exists. func (eksUtils eksDetectorUtils) fileExists(filename string) bool { info, err := os.Stat(filename) return err == nil && !info.IsDir() } // getConfigMap retrieves the configuration map from the k8s API. func (eksUtils eksDetectorUtils) getConfigMap(ctx context.Context, namespace string, name string) (map[string]string, error) { cm, err := eksUtils.clientset.CoreV1().ConfigMaps(namespace).Get(ctx, name, metav1.GetOptions{}) if err != nil { return nil, fmt.Errorf("failed to retrieve ConfigMap %s/%s: %w", namespace, name, err) } return cm.Data, nil } // getClusterName retrieves the clusterName resource attribute. func getClusterName(ctx context.Context, utils detectorUtils) (string, error) { resp, err := utils.getConfigMap(ctx, cwConfigmapNS, cwConfigmapName) if err != nil { return "", fmt.Errorf("getClusterName() error: %w", err) } return resp["cluster.name"], nil } // getContainerID returns the containerID if currently running within a container. func (eksUtils eksDetectorUtils) getContainerID() (string, error) { fileData, err := os.ReadFile(defaultCgroupPath) if err != nil { return "", fmt.Errorf("getContainerID() error: cannot read file with path %s: %w", defaultCgroupPath, err) } // is this going to stop working with 1.20 when Docker is deprecated? r, err := regexp.Compile(`^.*/docker/(.+)$`) if err != nil { return "", err } // Retrieve containerID from file splitData := strings.Split(strings.TrimSpace(string(fileData)), "\n") for _, str := range splitData { if r.MatchString(str) { return str[len(str)-containerIDLength:], nil } } return "", fmt.Errorf("getContainerID() error: cannot read containerID from file %s", defaultCgroupPath) } open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/aws/eks/detector_test.go000066400000000000000000000054241470323427300310250ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package eks import ( "context" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.26.0" ) type MockDetectorUtils struct { mock.Mock } // Mock function for fileExists(). func (detectorUtils *MockDetectorUtils) fileExists(filename string) bool { args := detectorUtils.Called(filename) return args.Bool(0) } // Mock function for getConfigMap(). func (detectorUtils *MockDetectorUtils) getConfigMap(_ context.Context, namespace string, name string) (map[string]string, error) { args := detectorUtils.Called(namespace, name) return args.Get(0).(map[string]string), args.Error(1) } // Mock function for getContainerID(). func (detectorUtils *MockDetectorUtils) getContainerID() (string, error) { args := detectorUtils.Called() return args.String(0), args.Error(1) } // Tests EKS resource detector running in EKS environment. func TestEks(t *testing.T) { detectorUtils := new(MockDetectorUtils) // Mock functions and set expectations detectorUtils.On("fileExists", k8sTokenPath).Return(true) detectorUtils.On("fileExists", k8sCertPath).Return(true) detectorUtils.On("getConfigMap", authConfigmapNS, authConfigmapName).Return(map[string]string{"not": "nil"}, nil) detectorUtils.On("getConfigMap", cwConfigmapNS, cwConfigmapName).Return(map[string]string{"cluster.name": "my-cluster"}, nil) detectorUtils.On("getContainerID").Return("0123456789A", nil) // Expected resource object eksResourceLabels := []attribute.KeyValue{ semconv.CloudProviderAWS, semconv.CloudPlatformAWSEKS, semconv.K8SClusterName("my-cluster"), semconv.ContainerID("0123456789A"), } expectedResource := resource.NewWithAttributes(semconv.SchemaURL, eksResourceLabels...) // Call EKS Resource detector to detect resources eksResourceDetector := resourceDetector{utils: detectorUtils} resourceObj, err := eksResourceDetector.Detect(context.Background()) require.NoError(t, err) assert.Equal(t, expectedResource, resourceObj, "Resource object returned is incorrect") detectorUtils.AssertExpectations(t) } // Tests EKS resource detector not running in EKS environment. func TestNotEKS(t *testing.T) { detectorUtils := new(MockDetectorUtils) k8sTokenPath := "/var/run/secrets/kubernetes.io/serviceaccount/token" // Mock functions and set expectations detectorUtils.On("fileExists", k8sTokenPath).Return(false) detector := resourceDetector{utils: detectorUtils} r, err := detector.Detect(context.Background()) require.NoError(t, err) assert.Equal(t, resource.Empty(), r, "Resource object should be empty") detectorUtils.AssertExpectations(t) } open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/aws/eks/go.mod000066400000000000000000000044351470323427300267350ustar00rootroot00000000000000module go.opentelemetry.io/contrib/detectors/aws/eks go 1.22.0 require ( github.com/stretchr/testify v1.9.0 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/sdk v1.31.0 k8s.io/apimachinery v0.31.1 k8s.io/client-go v0.31.1 ) require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.12.1 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/x448/float16 v0.8.4 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect go.opentelemetry.io/otel/trace v1.31.0 // indirect golang.org/x/net v0.30.0 // indirect golang.org/x/oauth2 v0.23.0 // indirect golang.org/x/sys v0.26.0 // indirect golang.org/x/term v0.25.0 // indirect golang.org/x/text v0.19.0 // indirect golang.org/x/time v0.7.0 // indirect google.golang.org/protobuf v1.35.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/api v0.31.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20241009091222-67ed5848f094 // indirect k8s.io/utils v0.0.0-20240921022957-49e7df575cb6 // indirect sigs.k8s.io/json v0.0.0-20241009153224-e386a8af8d30 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/aws/eks/go.sum000066400000000000000000000332111470323427300267540ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k= github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/api v0.31.1 h1:Xe1hX/fPW3PXYYv8BlozYqw63ytA92snr96zMW9gWTU= k8s.io/api v0.31.1/go.mod h1:sbN1g6eY6XVLeqNsZGLnI5FwVseTrZX7Fv3O26rhAaI= k8s.io/apimachinery v0.31.1 h1:mhcUBbj7KUjaVhyXILglcVjuS4nYXiwC+KKFBgIVy7U= k8s.io/apimachinery v0.31.1/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= k8s.io/client-go v0.31.1 h1:f0ugtWSbWpxHR7sjVpQwuvw9a3ZKLXX0u0itkFXufb0= k8s.io/client-go v0.31.1/go.mod h1:sKI8871MJN2OyeqRlmA4W4KM9KBdBUpDLu/43eGemCg= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20241009091222-67ed5848f094 h1:MErs8YA0abvOqJ8gIupA1Tz6PKXYUw34XsGlA7uSL1k= k8s.io/kube-openapi v0.0.0-20241009091222-67ed5848f094/go.mod h1:7ioBJr1A6igWjsR2fxq2EZ0mlMwYLejazSIc2bzMp2U= k8s.io/utils v0.0.0-20240921022957-49e7df575cb6 h1:MDF6h2H/h4tbzmtIKTuctcwZmY0tY9mD9fNT47QO6HI= k8s.io/utils v0.0.0-20240921022957-49e7df575cb6/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/json v0.0.0-20241009153224-e386a8af8d30 h1:ObU1vgTtAle8WwCKgcDkPjLJYwlazQpIjzSA0asMhy4= sigs.k8s.io/json v0.0.0-20241009153224-e386a8af8d30/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/aws/eks/version.go000066400000000000000000000007701470323427300276410ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package eks // import "go.opentelemetry.io/contrib/detectors/aws/eks" // Version is the current release version of the EKS resource detector. func Version() string { return "1.31.0" // This string is updated by the pre_release.sh script during release } // SemVersion is the semantic version to be supplied to tracer/meter creation. // // Deprecated: Use [Version] instead. func SemVersion() string { return Version() } open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/aws/lambda/000077500000000000000000000000001470323427300262575ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/aws/lambda/README.md000066400000000000000000000045071470323427300275440ustar00rootroot00000000000000# OpenTelemetry AWS Lambda Resource Detector for Golang [![Go Reference][goref-image]][goref-url] [![Apache License][license-image]][license-url] This module detects resource attributes available in AWS Lambda. ## Installation ```bash go get -u go.opentelemetry.io/contrib/detectors/aws/lambda ``` ## Usage Create a sample Lambda Go application such as below. ```go package main import ( "github.com/aws/aws-lambda-go/lambda" sdktrace "go.opencensus.io/otel/sdk/trace" lambdadetector "go.opentelemetry.io/contrib/detectors/aws/lambda" ) func main() { detector := lambdadetector.NewResourceDetector() res, err := detector.Detect(context.Background()) if err != nil { fmt.Printf("failed to detect lambda resources: %v\n", err) } tp := sdktrace.NewTracerProvider( sdktrace.WithResource(res), ) lambda.Start() } ``` Now your `TracerProvider` will have the following resource attributes and attach them to new spans: | Resource Attribute | Example Value | | --- | --- | | `cloud.provider` | aws |`cloud.region` | us-east-1 |`faas.name` | MyLambdaFunction |`faas.version` | $LATEST |`faas.instance` | 2021/06/28/[$LATEST]2f399eb14537447da05ab2a2e39309de |`faas.max_memory`| 128 Of note, `faas.id` and `cloud.account.id` are not set by the Lambda resource detector because they are not available outside a Lambda invocation. For this reason, when using the AWS Lambda Instrumentation these attributes are set as additional span attributes. ## Useful links - For more on FaaS attribute conventions, visit - For more information on OpenTelemetry, visit: - For more about OpenTelemetry Go: - For help or feedback on this project, join us in [GitHub Discussions][discussions-url] ## License Apache 2.0 - See [LICENSE][license-url] for more information. [license-url]: https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/LICENSE [license-image]: https://img.shields.io/badge/license-Apache_2.0-green.svg?style=flat [goref-image]: https://pkg.go.dev/badge/go.opentelemetry.io/contrib/detectors/aws/lambda.svg [goref-url]: https://pkg.go.dev/go.opentelemetry.io/contrib/detectors/aws/lambda [discussions-url]: https://github.com/open-telemetry/opentelemetry-go/discussions open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/aws/lambda/detector.go000066400000000000000000000047561470323427300304330ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package lambda // import "go.opentelemetry.io/contrib/detectors/aws/lambda" import ( "context" "errors" "os" "strconv" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.26.0" ) // For a complete list of reserved environment variables in Lambda, see: // https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html const ( lambdaFunctionNameEnvVar = "AWS_LAMBDA_FUNCTION_NAME" //nolint:gosec // False positive G101: Potential hardcoded credentials. The function name is added as attribute per semantic conventions. awsRegionEnvVar = "AWS_REGION" lambdaFunctionVersionEnvVar = "AWS_LAMBDA_FUNCTION_VERSION" lambdaLogStreamNameEnvVar = "AWS_LAMBDA_LOG_STREAM_NAME" lambdaMemoryLimitEnvVar = "AWS_LAMBDA_FUNCTION_MEMORY_SIZE" ) var ( empty = resource.Empty() errNotOnLambda = errors.New("process is not on Lambda, cannot detect environment variables from Lambda") ) // resource detector collects resource information from Lambda environment. type resourceDetector struct{} // compile time assertion that resource detector implements the resource.Detector interface. var _ resource.Detector = (*resourceDetector)(nil) // NewResourceDetector returns a resource detector that will detect AWS Lambda resources. func NewResourceDetector() resource.Detector { return &resourceDetector{} } // Detect collects resource attributes available when running on lambda. func (detector *resourceDetector) Detect(context.Context) (*resource.Resource, error) { // Lambda resources come from ENV lambdaName := os.Getenv(lambdaFunctionNameEnvVar) if len(lambdaName) == 0 { return empty, errNotOnLambda } awsRegion := os.Getenv(awsRegionEnvVar) functionVersion := os.Getenv(lambdaFunctionVersionEnvVar) // The instance attributes corresponds to the log stream name for AWS lambda, // see the FaaS resource specification for more details. instance := os.Getenv(lambdaLogStreamNameEnvVar) attrs := []attribute.KeyValue{ semconv.CloudProviderAWS, semconv.CloudRegion(awsRegion), semconv.FaaSInstance(instance), semconv.FaaSName(lambdaName), semconv.FaaSVersion(functionVersion), } maxMemoryStr := os.Getenv(lambdaMemoryLimitEnvVar) maxMemory, err := strconv.Atoi(maxMemoryStr) if err == nil { attrs = append(attrs, semconv.FaaSMaxMemory(maxMemory)) } return resource.NewWithAttributes(semconv.SchemaURL, attrs...), nil } open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/aws/lambda/detector_test.go000066400000000000000000000030451470323427300314600ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package lambda import ( "context" "os" "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.26.0" ) // successfully return resource when process is running on Amazon Lambda environment. func TestDetectSuccess(t *testing.T) { t.Setenv(lambdaFunctionNameEnvVar, "testFunction") t.Setenv(awsRegionEnvVar, "us-texas-1") t.Setenv(lambdaFunctionVersionEnvVar, "$LATEST") t.Setenv(lambdaLogStreamNameEnvVar, "2023/01/01/[$LATEST]5d1edb9e525d486696cf01a3503487bc") t.Setenv(lambdaMemoryLimitEnvVar, "128") attributes := []attribute.KeyValue{ semconv.CloudProviderAWS, semconv.CloudRegion("us-texas-1"), semconv.FaaSName("testFunction"), semconv.FaaSVersion("$LATEST"), semconv.FaaSInstance("2023/01/01/[$LATEST]5d1edb9e525d486696cf01a3503487bc"), semconv.FaaSMaxMemory(128), } expectedResource := resource.NewWithAttributes(semconv.SchemaURL, attributes...) detector := resourceDetector{} res, err := detector.Detect(context.Background()) assert.NoError(t, err, "Detector unexpectedly returned error") assert.Equal(t, expectedResource, res, "Resource returned is incorrect") } // return empty resource when not running on lambda. func TestReturnsIfNoEnvVars(t *testing.T) { os.Clearenv() detector := resourceDetector{} res, err := detector.Detect(context.Background()) assert.Equal(t, errNotOnLambda, err) assert.Empty(t, res.Attributes()) } open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/aws/lambda/go.mod000066400000000000000000000011371470323427300273670ustar00rootroot00000000000000module go.opentelemetry.io/contrib/detectors/aws/lambda go 1.22 require ( github.com/stretchr/testify v1.9.0 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/sdk v1.31.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect go.opentelemetry.io/otel/trace v1.31.0 // indirect golang.org/x/sys v0.26.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/aws/lambda/go.sum000066400000000000000000000046721470323427300274230ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/azure/000077500000000000000000000000001470323427300253735ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/azure/azurevm/000077500000000000000000000000001470323427300270645ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/azure/azurevm/README.md000066400000000000000000000003111470323427300303360ustar00rootroot00000000000000# Azure VM Resource detector open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/azure/azurevm/doc.go000066400000000000000000000015071470323427300301630ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 /* Package azurevm provides a [resource.Detector] which supports detecting attributes specific to Azure VMs. According to semantic conventions for [host], [cloud], and [os] attributes, each of the following attributes is added if it is available: - cloud.provider - cloud.platform - cloud.region - cloud.resource_id - host.id - host.name - host.type - os.type - os.version [host]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/resource/host.md [cloud]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/resource/cloud.md [os]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/resource/os.md */ package azurevm // import "go.opentelemetry.io/contrib/detectors/azure/azurevm" open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/azure/azurevm/example_new_test.go000066400000000000000000000007271470323427300327640ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package azurevm_test import ( "context" "fmt" "go.opentelemetry.io/contrib/detectors/azure/azurevm" ) func ExampleNew() { azureVMResourceDetector := azurevm.New() resource, err := azureVMResourceDetector.Detect(context.Background()) if err != nil { panic(err) } // Now, you can use the resource (e.g. pass it to a tracer or meter provider). fmt.Println(resource.SchemaURL()) } open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/azure/azurevm/go.mod000066400000000000000000000011421470323427300301700ustar00rootroot00000000000000module go.opentelemetry.io/contrib/detectors/azure/azurevm go 1.22 require ( github.com/stretchr/testify v1.9.0 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/sdk v1.31.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect go.opentelemetry.io/otel/trace v1.31.0 // indirect golang.org/x/sys v0.26.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/azure/azurevm/go.sum000066400000000000000000000046721470323427300302300ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/azure/azurevm/vm.go000066400000000000000000000060351470323427300300410ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package azurevm // import "go.opentelemetry.io/contrib/detectors/azure/azurevm" import ( "context" "encoding/json" "errors" "io" "net/http" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.26.0" ) const defaultAzureVMMetadataEndpoint = "http://169.254.169.254/metadata/instance/compute?api-version=2021-12-13&format=json" // ResourceDetector collects resource information of Azure VMs. type ResourceDetector struct { endpoint string } type vmMetadata struct { VMId *string `json:"vmId"` Location *string `json:"location"` ResourceId *string `json:"resourceId"` Name *string `json:"name"` VMSize *string `json:"vmSize"` OsType *string `json:"osType"` Version *string `json:"version"` } // New returns a [ResourceDetector] that will detect Azure VM resources. func New() *ResourceDetector { return &ResourceDetector{defaultAzureVMMetadataEndpoint} } // Detect detects associated resources when running on an Azure VM. func (detector *ResourceDetector) Detect(ctx context.Context) (*resource.Resource, error) { jsonMetadata, runningInAzure, err := detector.getJSONMetadata(ctx) if err != nil { if !runningInAzure { return resource.Empty(), nil } return nil, err } var metadata vmMetadata err = json.Unmarshal(jsonMetadata, &metadata) if err != nil { return nil, err } attributes := []attribute.KeyValue{ semconv.CloudProviderAzure, semconv.CloudPlatformAzureVM, } if metadata.VMId != nil { attributes = append(attributes, semconv.HostID(*metadata.VMId)) } if metadata.Location != nil { attributes = append(attributes, semconv.CloudRegion(*metadata.Location)) } if metadata.ResourceId != nil { attributes = append(attributes, semconv.CloudResourceID(*metadata.ResourceId)) } if metadata.Name != nil { attributes = append(attributes, semconv.HostName(*metadata.Name)) } if metadata.VMSize != nil { attributes = append(attributes, semconv.HostType(*metadata.VMSize)) } if metadata.OsType != nil { attributes = append(attributes, semconv.OSTypeKey.String(*metadata.OsType)) } if metadata.Version != nil { attributes = append(attributes, semconv.OSVersion(*metadata.Version)) } return resource.NewWithAttributes(semconv.SchemaURL, attributes...), nil } func (detector *ResourceDetector) getJSONMetadata(ctx context.Context) ([]byte, bool, error) { pTransport := &http.Transport{Proxy: nil} client := http.Client{Transport: pTransport} req, err := http.NewRequestWithContext(ctx, "GET", detector.endpoint, nil) if err != nil { return nil, false, err } req.Header.Add("Metadata", "True") resp, err := client.Do(req) if err != nil { return nil, false, err } defer resp.Body.Close() if resp.StatusCode == http.StatusOK { bytes, err := io.ReadAll(resp.Body) return bytes, true, err } runningInAzure := resp.StatusCode < 400 || resp.StatusCode > 499 return nil, runningInAzure, errors.New(http.StatusText(resp.StatusCode)) } open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/azure/azurevm/vm_test.go000066400000000000000000000047751470323427300311110ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package azurevm import ( "context" "fmt" "net/http" "net/http/httptest" "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.26.0" ) func TestDetect(t *testing.T) { type input struct { jsonMetadata string statusCode int } type expected struct { resource *resource.Resource err bool } type testCase struct { input input expected expected } testTable := []testCase{ { input: input{ jsonMetadata: `{ "location": "us-west3", "resourceId": "/subscriptions/sid/resourceGroups/rid/providers/pname/name", "vmId": "43f65c49-8715-4639-88a9-be6d7eb749a5", "name": "localhost-3", "vmSize": "Standard_D2s_v3", "osType": "linux", "version": "6.5.0-26-generic" }`, statusCode: http.StatusOK, }, expected: expected{ resource: resource.NewWithAttributes(semconv.SchemaURL, []attribute.KeyValue{ semconv.CloudProviderAzure, semconv.CloudPlatformAzureVM, semconv.CloudRegion("us-west3"), semconv.CloudResourceID("/subscriptions/sid/resourceGroups/rid/providers/pname/name"), semconv.HostID("43f65c49-8715-4639-88a9-be6d7eb749a5"), semconv.HostName("localhost-3"), semconv.HostType("Standard_D2s_v3"), semconv.OSTypeKey.String("linux"), semconv.OSVersion("6.5.0-26-generic"), }...), err: false, }, }, { input: input{ jsonMetadata: `{`, statusCode: http.StatusOK, }, expected: expected{ resource: nil, err: true, }, }, { input: input{ jsonMetadata: "", statusCode: http.StatusNotFound, }, expected: expected{ resource: resource.Empty(), err: false, }, }, { input: input{ jsonMetadata: "", statusCode: http.StatusInternalServerError, }, expected: expected{ resource: nil, err: true, }, }, } for _, tCase := range testTable { svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(tCase.input.statusCode) if r.Header.Get("Metadata") == "True" { fmt.Fprint(w, tCase.input.jsonMetadata) } })) detector := New() detector.endpoint = svr.URL azureResource, err := detector.Detect(context.Background()) svr.Close() assert.Equal(t, err != nil, tCase.expected.err) assert.Equal(t, tCase.expected.resource, azureResource) } } open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/gcp/000077500000000000000000000000001470323427300250165ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/gcp/README.md000066400000000000000000000043321470323427300262770ustar00rootroot00000000000000# GCP Resource detector The GCP resource detector supports detecting resources on: * Google Compute Engine (GCE) * Google Kubernetes Engine (GKE) * Google App Engine (GAE) * Cloud Run * Cloud Run jobs * Cloud Functions ## Usage ```golang ctx := context.Background() // Detect your resources res, err := resource.New(ctx, // Use the GCP resource detector! resource.WithDetectors(gcp.NewDetector()), // Keep the default detectors resource.WithTelemetrySDK(), // Add your own custom attributes to identify your application resource.WithAttributes( semconv.ServiceNameKey.String("my-application"), semconv.ServiceNamespaceKey.String("my-company-frontend-team"), ), ) if err != nil { // Handle err } // Use the resource in your tracerprovider (or meterprovider) tp := trace.NewTracerProvider( // ... other options trace.WithResource(res), ) ``` ## Setting Kubernetes attributes Previous iterations of GCP resource detection attempted to detect `container.name`, `k8s.pod.name` and `k8s.namespace.name`. When using this detector, you should use this in your Pod Spec to set these using [`OTEL_RESOURCE_ATTRIBUTES`](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.20.0/specification/resource/sdk.md#specifying-resource-information-via-an-environment-variable): ```yaml env: - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: NAMESPACE_NAME valueFrom: fieldRef: fieldPath: metadata.namespace - name: CONTAINER_NAME value: my-container-name - name: OTEL_RESOURCE_ATTRIBUTES value: k8s.pod.name=$(POD_NAME),k8s.namespace.name=$(NAMESPACE_NAME),k8s.container.name=$(CONTAINER_NAME) ``` To have a detector unpack the `OTEL_RESOURCE_ATTRIBUTES` envvar, use the `WithFromEnv` option: ```golang ... // Detect your resources res, err := resource.New(ctx, resource.WithDetectors(gcp.NewDetector()), resource.WithTelemetrySDK(), resource.WithFromEnv(), // unpacks OTEL_RESOURCE_ATTRIBUTES // Add your own custom attributes to identify your application resource.WithAttributes( semconv.ServiceNameKey.String("my-application"), semconv.ServiceNamespaceKey.String("my-company-frontend-team"), ), ) ... ``` open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/gcp/cloud-function.go000066400000000000000000000030001470323427300302670ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package gcp // import "go.opentelemetry.io/contrib/detectors/gcp" import ( "context" "os" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.26.0" ) const ( gcpFunctionNameKey = "K_SERVICE" ) // NewCloudFunction will return a GCP Cloud Function resource detector. // // Deprecated: Use gcp.NewDetector() instead, which sets the same resource attributes. func NewCloudFunction() resource.Detector { return &cloudFunction{ cloudRun: NewCloudRun(), } } // cloudFunction collects resource information of GCP Cloud Function. type cloudFunction struct { cloudRun *CloudRun } // Detect detects associated resources when running in GCP Cloud Function. func (f *cloudFunction) Detect(ctx context.Context) (*resource.Resource, error) { functionName, ok := f.googleCloudFunctionName() if !ok { return nil, nil } projectID, err := f.cloudRun.mc.ProjectID() if err != nil { return nil, err } region, err := f.cloudRun.cloudRegion() if err != nil { return nil, err } attributes := []attribute.KeyValue{ semconv.CloudProviderGCP, semconv.CloudPlatformGCPCloudFunctions, semconv.FaaSName(functionName), semconv.CloudAccountID(projectID), semconv.CloudRegion(region), } return resource.NewWithAttributes(semconv.SchemaURL, attributes...), nil } func (f *cloudFunction) googleCloudFunctionName() (string, bool) { return os.LookupEnv(gcpFunctionNameKey) } open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/gcp/cloud-function_test.go000066400000000000000000000066401470323427300313430ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package gcp import ( "context" "errors" "os" "testing" "github.com/google/go-cmp/cmp" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.26.0" ) var errTest = errors.New("testError") const ( projectIDValue = "some-projectID" regionValue = "some-region" functionName = "sample-function" ) type metaDataClientImpl struct { projectID func() (string, error) get func(string) (string, error) instanceID func() (string, error) } func (mock *metaDataClientImpl) ProjectID() (string, error) { if mock.projectID != nil { return mock.projectID() } return "", nil } func (mock *metaDataClientImpl) Get(key string) (string, error) { if mock.get != nil { return mock.get(key) } return "", nil } func (mock *metaDataClientImpl) InstanceID() (string, error) { if mock.instanceID != nil { return mock.instanceID() } return "", nil } type want struct { res *resource.Resource err error } func TestCloudFunctionDetect(t *testing.T) { oldValue, ok := os.LookupEnv(gcpFunctionNameKey) if !ok { err := os.Setenv(gcpFunctionNameKey, functionName) if err != nil { t.Error("unable to set environment variable ", err) } } defer func() { if !ok { _ = os.Unsetenv(gcpFunctionNameKey) } else { _ = os.Setenv(gcpFunctionNameKey, oldValue) } }() tests := []struct { name string cr *CloudRun expected want }{ { name: "error in reading ProjectID", cr: &CloudRun{ mc: &metaDataClientImpl{ projectID: func() (string, error) { return "", errTest }, }, }, expected: want{ res: nil, err: errTest, }, }, { name: "error in reading region", cr: &CloudRun{ mc: &metaDataClientImpl{ get: func(key string) (string, error) { return "", errTest }, }, }, expected: want{ res: nil, err: errTest, }, }, { name: "success", cr: &CloudRun{ mc: &metaDataClientImpl{ projectID: func() (string, error) { return projectIDValue, nil }, get: func(key string) (string, error) { return regionValue, nil }, }, }, expected: want{ res: resource.NewSchemaless([]attribute.KeyValue{ semconv.CloudProviderGCP, semconv.CloudPlatformGCPCloudFunctions, semconv.FaaSName(functionName), semconv.CloudAccountID(projectIDValue), semconv.CloudRegion(regionValue), }...), err: nil, }, }, } for _, test := range tests { detector := cloudFunction{ cloudRun: test.cr, } res, err := detector.Detect(context.Background()) if !errors.Is(err, test.expected.err) { t.Fatalf("got unexpected failure: %v", err) } else if diff := cmp.Diff(test.expected.res, res); diff != "" { t.Errorf("detected resource differ from expected (-want, +got)\n%s", diff) } } } func TestNotOnCloudFunction(t *testing.T) { oldValue, ok := os.LookupEnv(gcpFunctionNameKey) if ok { _ = os.Unsetenv(gcpFunctionNameKey) } defer func() { if ok { _ = os.Setenv(gcpFunctionNameKey, oldValue) } }() detector := NewCloudFunction() res, err := detector.Detect(context.Background()) if err != nil { t.Errorf("expected cloud function detector to return error as nil, but returned %v", err) } else if res != nil { t.Errorf("expected cloud function detector to return resource as nil, but returned %v", res) } } open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/gcp/cloud-run.go000066400000000000000000000066751470323427300272730ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package gcp // import "go.opentelemetry.io/contrib/detectors/gcp" import ( "context" "fmt" "os" "strings" "cloud.google.com/go/compute/metadata" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.26.0" ) const serviceNamespace = "cloud-run-managed" // The minimal list of metadata.Client methods we use. Use an interface so we // can replace it with a fake implementation in the unit test. type metadataClient interface { ProjectID() (string, error) Get(string) (string, error) InstanceID() (string, error) } // CloudRun collects resource information of Cloud Run instance. // // Deprecated: Use gcp.NewDetector() instead. Note that it sets faas.* resource attributes instead of service.* attributes. type CloudRun struct { mc metadataClient onGCE func() bool getenv func(string) string } // compile time assertion that CloudRun implements the resource.Detector // interface. var _ resource.Detector = (*CloudRun)(nil) // NewCloudRun creates a CloudRun detector. // // Deprecated: Use gcp.NewDetector() instead. Note that it sets faas.* resource attributes instead of service.* attributes. func NewCloudRun() *CloudRun { return &CloudRun{ mc: metadata.NewClient(nil), onGCE: metadata.OnGCE, getenv: os.Getenv, } } func (c *CloudRun) cloudRegion() (string, error) { region, err := c.mc.Get("instance/region") if err != nil { return "", err } // Region from the metadata server is in the format /projects/123/regions/r. // https://cloud.google.com/run/docs/reference/container-contract#metadata-server return region[strings.LastIndex(region, "/")+1:], nil } // Detect detects associated resources when running on Cloud Run hosts. // NOTE: the service.namespace attribute is currently hardcoded to be // "cloud-run-managed". This may change in the future, please do not rely on // this behavior yet. func (c *CloudRun) Detect(ctx context.Context) (*resource.Resource, error) { // .OnGCE is actually testing whether the metadata server is available. // Metadata server is supported on Cloud Run. if !c.onGCE() { return nil, nil } attributes := []attribute.KeyValue{ semconv.CloudProviderGCP, semconv.ServiceNamespace(serviceNamespace), } var errInfo []string if projectID, err := c.mc.ProjectID(); hasProblem(err) { errInfo = append(errInfo, err.Error()) } else if projectID != "" { attributes = append(attributes, semconv.CloudAccountID(projectID)) } if region, err := c.cloudRegion(); hasProblem(err) { errInfo = append(errInfo, err.Error()) } else if region != "" { attributes = append(attributes, semconv.CloudRegion(region)) } if instanceID, err := c.mc.InstanceID(); hasProblem(err) { errInfo = append(errInfo, err.Error()) } else if instanceID != "" { attributes = append(attributes, semconv.ServiceInstanceID(instanceID)) } // Part of Cloud Run container runtime contract. // See https://cloud.google.com/run/docs/reference/container-contract if service := c.getenv("K_SERVICE"); service == "" { errInfo = append(errInfo, "envvar K_SERVICE contains empty string.") } else { attributes = append(attributes, semconv.ServiceName(service)) } res := resource.NewWithAttributes(semconv.SchemaURL, attributes...) var aggregatedErr error if len(errInfo) > 0 { aggregatedErr = fmt.Errorf("detecting Cloud Run resources: %s", errInfo) } return res, aggregatedErr } open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/gcp/cloud-run_test.go000066400000000000000000000073061470323427300303220ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package gcp import ( "context" "fmt" "testing" "github.com/google/go-cmp/cmp" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" ) var ( notOnGCE = func() bool { return false } onGCE = func() bool { return true } ) func getenv(m map[string]string) func(string) string { return func(s string) string { if m == nil { return "" } return m[s] } } type client struct { m map[string]string } func setupForTest(c *CloudRun, mc metadataClient, ongce func() bool, getenv func(string) string) { c.mc = mc c.onGCE = ongce c.getenv = getenv } func (c *client) Get(s string) (string, error) { got, ok := c.m[s] if !ok { return "", fmt.Errorf("%q do not exist", s) } else if got == "" { return "", fmt.Errorf("%q is empty", s) } return got, nil } func (c *client) InstanceID() (string, error) { return c.Get("instance/id") } func (c *client) ProjectID() (string, error) { return c.Get("project/project-id") } var _ metadataClient = (*client)(nil) func TestCloudRunDetectorNotOnGCE(t *testing.T) { ctx := context.Background() c := NewCloudRun() setupForTest(c, nil, notOnGCE, getenv(nil)) if res, err := c.Detect(ctx); res != nil || err != nil { t.Errorf("Expect c.Detect(ctx) to return (nil, nil), got (%v, %v)", res, err) } } func TestCloudRunDetectorExpectSuccess(t *testing.T) { ctx := context.Background() metadata := map[string]string{ "project/project-id": "foo", "instance/id": "bar", "instance/region": "/projects/123/regions/utopia", } envvars := map[string]string{ "K_SERVICE": "x-service", } want, err := resource.New( ctx, resource.WithAttributes( attribute.String("cloud.account.id", "foo"), attribute.String("cloud.provider", "gcp"), attribute.String("cloud.region", "utopia"), attribute.String("service.instance.id", "bar"), attribute.String("service.name", "x-service"), attribute.String("service.namespace", "cloud-run-managed"), ), ) if err != nil { t.Fatalf("failed to create a resource: %v", err) } c := NewCloudRun() setupForTest(c, &client{m: metadata}, onGCE, getenv(envvars)) if res, err := c.Detect(ctx); err != nil { t.Fatalf("got unexpected failure: %v", err) } else if diff := cmp.Diff(want, res); diff != "" { t.Errorf("detected resource differ from expected (-want, +got)\n%s", diff) } } func TestCloudRunDetectorExpectFail(t *testing.T) { ctx := context.Background() tests := []struct { name string metadata map[string]string envvars map[string]string }{ { name: "Missing ProjectID", metadata: map[string]string{ "instance/id": "bar", "instance/region": "utopia", }, envvars: map[string]string{ "K_SERVICE": "x-service", }, }, { name: "Missing InstanceID", metadata: map[string]string{ "project/project-id": "foo", "instance/region": "utopia", }, envvars: map[string]string{ "K_SERVICE": "x-service", }, }, { name: "Missing Region", metadata: map[string]string{ "project/project-id": "foo", "instance/id": "bar", }, envvars: map[string]string{ "K_SERVICE": "x-service", }, }, { name: "Missing K_SERVICE envvar", metadata: map[string]string{ "project/project-id": "foo", "instance/id": "bar", "instance/region": "utopia", }, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { c := NewCloudRun() setupForTest(c, &client{m: test.metadata}, onGCE, getenv(test.envvars)) if res, err := c.Detect(ctx); err == nil { t.Errorf("Expect c.Detect(ctx) to return error, got nil (resource: %v)", res) } else { t.Logf("err: %v", err) } }) } } open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/gcp/detector.go000066400000000000000000000127221470323427300271620ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package gcp // import "go.opentelemetry.io/contrib/detectors/gcp" import ( "context" "fmt" "strconv" "cloud.google.com/go/compute/metadata" "github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.26.0" ) // NewDetector returns a resource detector which detects resource attributes on: // * Google Compute Engine (GCE). // * Google Kubernetes Engine (GKE). // * Google App Engine (GAE). // * Cloud Run. // * Cloud Functions. func NewDetector() resource.Detector { return &detector{detector: gcp.NewDetector()} } type detector struct { detector gcpDetector } // Detect detects associated resources when running on GCE, GKE, GAE, // Cloud Run, and Cloud functions. func (d *detector) Detect(ctx context.Context) (*resource.Resource, error) { if !metadata.OnGCE() { return nil, nil } b := &resourceBuilder{} b.attrs = append(b.attrs, semconv.CloudProviderGCP) b.add(semconv.CloudAccountIDKey, d.detector.ProjectID) switch d.detector.CloudPlatform() { case gcp.GKE: b.attrs = append(b.attrs, semconv.CloudPlatformGCPKubernetesEngine) b.addZoneOrRegion(d.detector.GKEAvailabilityZoneOrRegion) b.add(semconv.K8SClusterNameKey, d.detector.GKEClusterName) b.add(semconv.HostIDKey, d.detector.GKEHostID) case gcp.CloudRun: b.attrs = append(b.attrs, semconv.CloudPlatformGCPCloudRun) b.add(semconv.FaaSNameKey, d.detector.FaaSName) b.add(semconv.FaaSVersionKey, d.detector.FaaSVersion) b.add(semconv.FaaSInstanceKey, d.detector.FaaSID) b.add(semconv.CloudRegionKey, d.detector.FaaSCloudRegion) case gcp.CloudRunJob: b.attrs = append(b.attrs, semconv.CloudPlatformGCPCloudRun) b.add(semconv.FaaSNameKey, d.detector.FaaSName) b.add(semconv.FaaSInstanceKey, d.detector.FaaSID) b.add(semconv.GCPCloudRunJobExecutionKey, d.detector.CloudRunJobExecution) b.addInt(semconv.GCPCloudRunJobTaskIndexKey, d.detector.CloudRunJobTaskIndex) b.add(semconv.CloudRegionKey, d.detector.FaaSCloudRegion) case gcp.CloudFunctions: b.attrs = append(b.attrs, semconv.CloudPlatformGCPCloudFunctions) b.add(semconv.FaaSNameKey, d.detector.FaaSName) b.add(semconv.FaaSVersionKey, d.detector.FaaSVersion) b.add(semconv.FaaSInstanceKey, d.detector.FaaSID) b.add(semconv.CloudRegionKey, d.detector.FaaSCloudRegion) case gcp.AppEngineFlex: b.attrs = append(b.attrs, semconv.CloudPlatformGCPAppEngine) b.addZoneAndRegion(d.detector.AppEngineFlexAvailabilityZoneAndRegion) b.add(semconv.FaaSNameKey, d.detector.AppEngineServiceName) b.add(semconv.FaaSVersionKey, d.detector.AppEngineServiceVersion) b.add(semconv.FaaSInstanceKey, d.detector.AppEngineServiceInstance) case gcp.AppEngineStandard: b.attrs = append(b.attrs, semconv.CloudPlatformGCPAppEngine) b.add(semconv.CloudAvailabilityZoneKey, d.detector.AppEngineStandardAvailabilityZone) b.add(semconv.CloudRegionKey, d.detector.AppEngineStandardCloudRegion) b.add(semconv.FaaSNameKey, d.detector.AppEngineServiceName) b.add(semconv.FaaSVersionKey, d.detector.AppEngineServiceVersion) b.add(semconv.FaaSInstanceKey, d.detector.AppEngineServiceInstance) case gcp.GCE: b.attrs = append(b.attrs, semconv.CloudPlatformGCPComputeEngine) b.addZoneAndRegion(d.detector.GCEAvailabilityZoneAndRegion) b.add(semconv.HostTypeKey, d.detector.GCEHostType) b.add(semconv.HostIDKey, d.detector.GCEHostID) b.add(semconv.HostNameKey, d.detector.GCEHostName) b.add(semconv.GCPGceInstanceNameKey, d.detector.GCEInstanceName) b.add(semconv.GCPGceInstanceHostnameKey, d.detector.GCEInstanceHostname) default: // We don't support this platform yet, so just return with what we have } return b.build() } // resourceBuilder simplifies constructing resources using GCP detection // library functions. type resourceBuilder struct { errs []error attrs []attribute.KeyValue } func (r *resourceBuilder) add(key attribute.Key, detect func() (string, error)) { if v, err := detect(); err == nil { r.attrs = append(r.attrs, key.String(v)) } else { r.errs = append(r.errs, err) } } func (r *resourceBuilder) addInt(key attribute.Key, detect func() (string, error)) { if v, err := detect(); err == nil { if vi, err := strconv.Atoi(v); err == nil { r.attrs = append(r.attrs, key.Int(vi)) } else { r.errs = append(r.errs, err) } } else { r.errs = append(r.errs, err) } } // zoneAndRegion functions are expected to return zone, region, err. func (r *resourceBuilder) addZoneAndRegion(detect func() (string, string, error)) { if zone, region, err := detect(); err == nil { r.attrs = append(r.attrs, semconv.CloudAvailabilityZone(zone)) r.attrs = append(r.attrs, semconv.CloudRegion(region)) } else { r.errs = append(r.errs, err) } } func (r *resourceBuilder) addZoneOrRegion(detect func() (string, gcp.LocationType, error)) { if v, locType, err := detect(); err == nil { switch locType { case gcp.Zone: r.attrs = append(r.attrs, semconv.CloudAvailabilityZone(v)) case gcp.Region: r.attrs = append(r.attrs, semconv.CloudRegion(v)) default: r.errs = append(r.errs, fmt.Errorf("location must be zone or region. Got %v", locType)) } } else { r.errs = append(r.errs, err) } } func (r *resourceBuilder) build() (*resource.Resource, error) { var err error if len(r.errs) > 0 { err = fmt.Errorf("%w: %s", resource.ErrPartialResource, r.errs) } return resource.NewWithAttributes(semconv.SchemaURL, r.attrs...), err } open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/gcp/detector_test.go000066400000000000000000000310011470323427300302100ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package gcp // import "go.opentelemetry.io/contrib/detectors/gcp" import ( "context" "fmt" "testing" "github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.26.0" ) func TestDetect(t *testing.T) { // Set this before all tests to ensure metadata.onGCE() returns true t.Setenv("GCE_METADATA_HOST", "169.254.169.254") for _, tc := range []struct { desc string detector resource.Detector expectErr bool expectedResource *resource.Resource }{ { desc: "zonal GKE cluster", detector: &detector{detector: &fakeGCPDetector{ projectID: "my-project", cloudPlatform: gcp.GKE, gkeHostID: "1472385723456792345", gkeClusterName: "my-cluster", gkeAvailabilityZone: "us-central1-c", }}, expectedResource: resource.NewWithAttributes(semconv.SchemaURL, semconv.CloudProviderGCP, semconv.CloudAccountID("my-project"), semconv.CloudPlatformGCPKubernetesEngine, semconv.K8SClusterName("my-cluster"), semconv.CloudAvailabilityZone("us-central1-c"), semconv.HostID("1472385723456792345"), ), }, { desc: "regional GKE cluster", detector: &detector{detector: &fakeGCPDetector{ projectID: "my-project", cloudPlatform: gcp.GKE, gkeHostID: "1472385723456792345", gkeClusterName: "my-cluster", gkeRegion: "us-central1", }}, expectedResource: resource.NewWithAttributes(semconv.SchemaURL, semconv.CloudProviderGCP, semconv.CloudAccountID("my-project"), semconv.CloudPlatformGCPKubernetesEngine, semconv.K8SClusterName("my-cluster"), semconv.CloudRegion("us-central1"), semconv.HostID("1472385723456792345"), ), }, { desc: "GCE", detector: &detector{detector: &fakeGCPDetector{ projectID: "my-project", cloudPlatform: gcp.GCE, gceHostID: "1472385723456792345", gceHostName: "my-gke-node-1234", gceHostType: "n1-standard1", gceAvailabilityZone: "us-central1-c", gceRegion: "us-central1", gcpGceInstanceName: "my-gke-node-1234", gcpGceInstanceHostname: "hostname", }}, expectedResource: resource.NewWithAttributes(semconv.SchemaURL, semconv.CloudProviderGCP, semconv.CloudAccountID("my-project"), semconv.CloudPlatformGCPComputeEngine, semconv.HostID("1472385723456792345"), semconv.HostName("my-gke-node-1234"), semconv.GCPGceInstanceNameKey.String("my-gke-node-1234"), semconv.GCPGceInstanceHostnameKey.String("hostname"), semconv.HostType("n1-standard1"), semconv.CloudRegion("us-central1"), semconv.CloudAvailabilityZone("us-central1-c"), ), }, { desc: "Cloud Run", detector: &detector{detector: &fakeGCPDetector{ projectID: "my-project", cloudPlatform: gcp.CloudRun, faaSID: "1472385723456792345", faaSCloudRegion: "us-central1", faaSName: "my-service", faaSVersion: "123456", }}, expectedResource: resource.NewWithAttributes(semconv.SchemaURL, semconv.CloudProviderGCP, semconv.CloudAccountID("my-project"), semconv.CloudPlatformGCPCloudRun, semconv.CloudRegion("us-central1"), semconv.FaaSName("my-service"), semconv.FaaSVersion("123456"), semconv.FaaSInstance("1472385723456792345"), ), }, { desc: "Cloud Run Job", detector: &detector{detector: &fakeGCPDetector{ projectID: "my-project", cloudPlatform: gcp.CloudRunJob, faaSID: "1472385723456792345", faaSCloudRegion: "us-central1", faaSName: "my-service", cloudRunJobExecution: "my-service-ekdih", cloudRunJobTaskIndex: "0", }}, expectedResource: resource.NewWithAttributes(semconv.SchemaURL, semconv.CloudProviderGCP, semconv.CloudAccountID("my-project"), semconv.CloudPlatformGCPCloudRun, semconv.CloudRegion("us-central1"), semconv.FaaSName("my-service"), semconv.GCPCloudRunJobExecution("my-service-ekdih"), semconv.GCPCloudRunJobTaskIndex(0), semconv.FaaSInstance("1472385723456792345"), ), }, { desc: "Cloud Run Job Bad Index", detector: &detector{detector: &fakeGCPDetector{ projectID: "my-project", cloudPlatform: gcp.CloudRunJob, faaSID: "1472385723456792345", faaSCloudRegion: "us-central1", faaSName: "my-service", cloudRunJobExecution: "my-service-ekdih", cloudRunJobTaskIndex: "bad-value", }}, expectedResource: resource.NewWithAttributes(semconv.SchemaURL, semconv.CloudProviderGCP, semconv.CloudAccountID("my-project"), semconv.CloudPlatformGCPCloudRun, semconv.CloudRegion("us-central1"), semconv.FaaSName("my-service"), semconv.GCPCloudRunJobExecution("my-service-ekdih"), semconv.FaaSInstance("1472385723456792345"), ), expectErr: true, }, { desc: "Cloud Functions", detector: &detector{detector: &fakeGCPDetector{ projectID: "my-project", cloudPlatform: gcp.CloudFunctions, faaSID: "1472385723456792345", faaSCloudRegion: "us-central1", faaSName: "my-service", faaSVersion: "123456", }}, expectedResource: resource.NewWithAttributes(semconv.SchemaURL, semconv.CloudProviderGCP, semconv.CloudAccountID("my-project"), semconv.CloudPlatformGCPCloudFunctions, semconv.CloudRegion("us-central1"), semconv.FaaSName("my-service"), semconv.FaaSVersion("123456"), semconv.FaaSInstance("1472385723456792345"), ), }, { desc: "App Engine Flex", detector: &detector{detector: &fakeGCPDetector{ projectID: "my-project", cloudPlatform: gcp.AppEngineFlex, appEngineServiceInstance: "1472385723456792345", appEngineAvailabilityZone: "us-central1-c", appEngineRegion: "us-central1", appEngineServiceName: "my-service", appEngineServiceVersion: "123456", }}, expectedResource: resource.NewWithAttributes(semconv.SchemaURL, semconv.CloudProviderGCP, semconv.CloudAccountID("my-project"), semconv.CloudPlatformGCPAppEngine, semconv.CloudRegion("us-central1"), semconv.CloudAvailabilityZone("us-central1-c"), semconv.FaaSName("my-service"), semconv.FaaSVersion("123456"), semconv.FaaSInstance("1472385723456792345"), ), }, { desc: "App Engine Standard", detector: &detector{detector: &fakeGCPDetector{ projectID: "my-project", cloudPlatform: gcp.AppEngineStandard, appEngineServiceInstance: "1472385723456792345", appEngineAvailabilityZone: "us-central1-c", appEngineRegion: "us-central1", appEngineServiceName: "my-service", appEngineServiceVersion: "123456", }}, expectedResource: resource.NewWithAttributes(semconv.SchemaURL, semconv.CloudProviderGCP, semconv.CloudAccountID("my-project"), semconv.CloudPlatformGCPAppEngine, semconv.CloudRegion("us-central1"), semconv.CloudAvailabilityZone("us-central1-c"), semconv.FaaSName("my-service"), semconv.FaaSVersion("123456"), semconv.FaaSInstance("1472385723456792345"), ), }, { desc: "Unknown Platform", detector: &detector{detector: &fakeGCPDetector{ projectID: "my-project", cloudPlatform: gcp.UnknownPlatform, }}, expectedResource: resource.NewWithAttributes(semconv.SchemaURL, semconv.CloudProviderGCP, semconv.CloudAccountID("my-project"), ), }, { desc: "error", detector: &detector{detector: &fakeGCPDetector{ err: fmt.Errorf("failed to get metadata"), }}, expectErr: true, expectedResource: resource.NewWithAttributes(semconv.SchemaURL, semconv.CloudProviderGCP, ), }, } { t.Run(tc.desc, func(t *testing.T) { res, err := tc.detector.Detect(context.TODO()) if tc.expectErr { assert.Error(t, err) } else { assert.NoError(t, err) } assert.Equal(t, tc.expectedResource, res, "Resource object returned is incorrect") }) } } // fakeGCPDetector implements gcpDetector and uses fake values. type fakeGCPDetector struct { err error projectID string cloudPlatform gcp.Platform gkeAvailabilityZone string gkeRegion string gkeClusterName string gkeHostID string gkeHostName string faaSName string faaSVersion string faaSID string faaSCloudRegion string appEngineAvailabilityZone string appEngineRegion string appEngineServiceName string appEngineServiceVersion string appEngineServiceInstance string gceAvailabilityZone string gceRegion string gceHostType string gceHostID string gceHostName string gcpGceInstanceName string gcpGceInstanceHostname string cloudRunJobExecution string cloudRunJobTaskIndex string } func (f *fakeGCPDetector) ProjectID() (string, error) { if f.err != nil { return "", f.err } return f.projectID, nil } func (f *fakeGCPDetector) CloudPlatform() gcp.Platform { return f.cloudPlatform } func (f *fakeGCPDetector) GKEAvailabilityZoneOrRegion() (string, gcp.LocationType, error) { if f.err != nil { return "", gcp.UndefinedLocation, f.err } if f.gkeAvailabilityZone != "" { return f.gkeAvailabilityZone, gcp.Zone, nil } return f.gkeRegion, gcp.Region, nil } func (f *fakeGCPDetector) GKEClusterName() (string, error) { if f.err != nil { return "", f.err } return f.gkeClusterName, nil } func (f *fakeGCPDetector) GKEHostID() (string, error) { if f.err != nil { return "", f.err } return f.gkeHostID, nil } func (f *fakeGCPDetector) GKEHostName() (string, error) { if f.err != nil { return "", f.err } return f.gkeHostName, nil } func (f *fakeGCPDetector) FaaSName() (string, error) { if f.err != nil { return "", f.err } return f.faaSName, nil } func (f *fakeGCPDetector) FaaSVersion() (string, error) { if f.err != nil { return "", f.err } return f.faaSVersion, nil } func (f *fakeGCPDetector) FaaSID() (string, error) { if f.err != nil { return "", f.err } return f.faaSID, nil } func (f *fakeGCPDetector) FaaSCloudRegion() (string, error) { if f.err != nil { return "", f.err } return f.faaSCloudRegion, nil } func (f *fakeGCPDetector) AppEngineFlexAvailabilityZoneAndRegion() (string, string, error) { if f.err != nil { return "", "", f.err } return f.appEngineAvailabilityZone, f.appEngineRegion, nil } func (f *fakeGCPDetector) AppEngineStandardAvailabilityZone() (string, error) { if f.err != nil { return "", f.err } return f.appEngineAvailabilityZone, nil } func (f *fakeGCPDetector) AppEngineStandardCloudRegion() (string, error) { if f.err != nil { return "", f.err } return f.appEngineRegion, nil } func (f *fakeGCPDetector) AppEngineServiceName() (string, error) { if f.err != nil { return "", f.err } return f.appEngineServiceName, nil } func (f *fakeGCPDetector) AppEngineServiceVersion() (string, error) { if f.err != nil { return "", f.err } return f.appEngineServiceVersion, nil } func (f *fakeGCPDetector) AppEngineServiceInstance() (string, error) { if f.err != nil { return "", f.err } return f.appEngineServiceInstance, nil } func (f *fakeGCPDetector) GCEAvailabilityZoneAndRegion() (string, string, error) { if f.err != nil { return "", "", f.err } return f.gceAvailabilityZone, f.gceRegion, nil } func (f *fakeGCPDetector) GCEHostType() (string, error) { if f.err != nil { return "", f.err } return f.gceHostType, nil } func (f *fakeGCPDetector) GCEHostID() (string, error) { if f.err != nil { return "", f.err } return f.gceHostID, nil } func (f *fakeGCPDetector) GCEHostName() (string, error) { if f.err != nil { return "", f.err } return f.gceHostName, nil } func (f *fakeGCPDetector) GCEInstanceName() (string, error) { if f.err != nil { return "", f.err } return f.gcpGceInstanceName, nil } func (f *fakeGCPDetector) GCEInstanceHostname() (string, error) { if f.err != nil { return "", f.err } return f.gcpGceInstanceHostname, nil } func (f *fakeGCPDetector) CloudRunJobExecution() (string, error) { if f.err != nil { return "", f.err } return f.cloudRunJobExecution, nil } func (f *fakeGCPDetector) CloudRunJobTaskIndex() (string, error) { if f.err != nil { return "", f.err } return f.cloudRunJobTaskIndex, nil } open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/gcp/gce.go000066400000000000000000000054311470323427300261060ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package gcp // import "go.opentelemetry.io/contrib/detectors/gcp" import ( "context" "errors" "fmt" "os" "strings" "cloud.google.com/go/compute/metadata" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.26.0" ) // GCE collects resource information of GCE computing instances. // // Deprecated: Use gcp.NewDetector() instead, which sets the same resource attributes on GCE. type GCE struct{} // compile time assertion that GCE implements the resource.Detector interface. var _ resource.Detector = (*GCE)(nil) // Detect detects associated resources when running on GCE hosts. func (gce *GCE) Detect(ctx context.Context) (*resource.Resource, error) { if !metadata.OnGCE() { return nil, nil } attributes := []attribute.KeyValue{ semconv.CloudProviderGCP, } var errInfo []string if projectID, err := metadata.ProjectIDWithContext(ctx); hasProblem(err) { errInfo = append(errInfo, err.Error()) } else if projectID != "" { attributes = append(attributes, semconv.CloudAccountID(projectID)) } if zone, err := metadata.ZoneWithContext(ctx); hasProblem(err) { errInfo = append(errInfo, err.Error()) } else if zone != "" { attributes = append(attributes, semconv.CloudAvailabilityZone(zone)) splitArr := strings.SplitN(zone, "-", 3) if len(splitArr) == 3 { attributes = append(attributes, semconv.CloudRegion(strings.Join(splitArr[0:2], "-"))) } } if instanceID, err := metadata.InstanceIDWithContext(ctx); hasProblem(err) { errInfo = append(errInfo, err.Error()) } else if instanceID != "" { attributes = append(attributes, semconv.HostID(instanceID)) } if name, err := metadata.InstanceNameWithContext(ctx); hasProblem(err) { errInfo = append(errInfo, err.Error()) } else if name != "" { attributes = append(attributes, semconv.HostName(name)) } if hostname, err := os.Hostname(); hasProblem(err) { errInfo = append(errInfo, err.Error()) } else if hostname != "" { attributes = append(attributes, semconv.HostName(hostname)) } if hostType, err := metadata.GetWithContext(ctx, "instance/machine-type"); hasProblem(err) { errInfo = append(errInfo, err.Error()) } else if hostType != "" { attributes = append(attributes, semconv.HostType(hostType)) } var aggregatedErr error if len(errInfo) > 0 { aggregatedErr = fmt.Errorf("detecting GCE resources: %s", errInfo) } return resource.NewWithAttributes(semconv.SchemaURL, attributes...), aggregatedErr } // hasProblem checks if the err is not nil or for missing resources. func hasProblem(err error) bool { if err == nil { return false } var nde metadata.NotDefinedError if undefined := errors.As(err, &nde); undefined { return false } return true } open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/gcp/gke.go000066400000000000000000000037321470323427300261200ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package gcp // import "go.opentelemetry.io/contrib/detectors/gcp" import ( "context" "fmt" "os" "cloud.google.com/go/compute/metadata" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.26.0" ) // GKE collects resource information of GKE computing instances. // // Deprecated: Use gcp.NewDetector() instead, which does NOT detect container, pod, and namespace attributes. // Set those using name using the OTEL_RESOURCE_ATTRIBUTES env var instead. type GKE struct{} // compile time assertion that GKE implements the resource.Detector interface. var _ resource.Detector = (*GKE)(nil) // Detect detects associated resources when running in GKE environment. func (gke *GKE) Detect(ctx context.Context) (*resource.Resource, error) { gcpDetecor := GCE{} gceLablRes, err := gcpDetecor.Detect(ctx) if os.Getenv("KUBERNETES_SERVICE_HOST") == "" { return gceLablRes, err } var errInfo []string if err != nil { errInfo = append(errInfo, err.Error()) } attributes := []attribute.KeyValue{ semconv.K8SNamespaceName(os.Getenv("NAMESPACE")), semconv.K8SPodName(os.Getenv("HOSTNAME")), } if containerName := os.Getenv("CONTAINER_NAME"); containerName != "" { attributes = append(attributes, semconv.ContainerName(containerName)) } if clusterName, err := metadata.InstanceAttributeValueWithContext(ctx, "cluster-name"); hasProblem(err) { errInfo = append(errInfo, err.Error()) } else if clusterName != "" { attributes = append(attributes, semconv.K8SClusterName(clusterName)) } k8sattributeRes := resource.NewWithAttributes(semconv.SchemaURL, attributes...) res, err := resource.Merge(gceLablRes, k8sattributeRes) if err != nil { errInfo = append(errInfo, err.Error()) } var aggregatedErr error if len(errInfo) > 0 { aggregatedErr = fmt.Errorf("detecting GKE resources: %s", errInfo) } return res, aggregatedErr } open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/gcp/go.mod000066400000000000000000000014371470323427300261310ustar00rootroot00000000000000module go.opentelemetry.io/contrib/detectors/gcp go 1.22 require ( cloud.google.com/go/compute/metadata v0.5.2 github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.24.2 github.com/google/go-cmp v0.6.0 github.com/stretchr/testify v1.9.0 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/sdk v1.31.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/kr/text v0.2.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect go.opentelemetry.io/otel/trace v1.31.0 // indirect golang.org/x/sys v0.26.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/gcp/go.sum000066400000000000000000000070631470323427300261570ustar00rootroot00000000000000cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo= cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.24.2 h1:cZpsGsWTIFKymTA0je7IIvi1O7Es7apb9CF3EQlOcfE= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.24.2/go.mod h1:itPGVDKf9cC/ov4MdvJ2QZ0khw4bfoo9jzwTJlaxy2k= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/gcp/types.go000066400000000000000000000023041470323427300265100ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package gcp // import "go.opentelemetry.io/contrib/detectors/gcp" import "github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp" // gcpDetector can detect attributes of GCP environments. type gcpDetector interface { ProjectID() (string, error) CloudPlatform() gcp.Platform GKEAvailabilityZoneOrRegion() (string, gcp.LocationType, error) GKEClusterName() (string, error) GKEHostID() (string, error) FaaSName() (string, error) FaaSVersion() (string, error) FaaSID() (string, error) FaaSCloudRegion() (string, error) AppEngineFlexAvailabilityZoneAndRegion() (string, string, error) AppEngineStandardAvailabilityZone() (string, error) AppEngineStandardCloudRegion() (string, error) AppEngineServiceName() (string, error) AppEngineServiceVersion() (string, error) AppEngineServiceInstance() (string, error) GCEAvailabilityZoneAndRegion() (string, string, error) GCEHostType() (string, error) GCEHostID() (string, error) GCEHostName() (string, error) GCEInstanceHostname() (string, error) GCEInstanceName() (string, error) CloudRunJobExecution() (string, error) CloudRunJobTaskIndex() (string, error) } open-telemetry-opentelemetry-go-contrib-e5abccb/detectors/gcp/version.go000066400000000000000000000007641470323427300270410ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package gcp // import "go.opentelemetry.io/contrib/detectors/gcp" // Version is the current release version of the GCP resource detector. func Version() string { return "1.31.0" // This string is updated by the pre_release.sh script during release } // SemVersion is the semantic version to be supplied to tracer/meter creation. // // Deprecated: Use [Version] instead. func SemVersion() string { return Version() } open-telemetry-opentelemetry-go-contrib-e5abccb/doc.go000066400000000000000000000005151470323427300233460ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package contrib is a collection of extensions for the opentelemetry-go // project. It provides 3rd party resource detectors, propagators, samplers, // bridges, and instrumentation as submodules. package contrib // import "go.opentelemetry.io/contrib" open-telemetry-opentelemetry-go-contrib-e5abccb/examples/000077500000000000000000000000001470323427300240675ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/examples/dice/000077500000000000000000000000001470323427300247735ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/examples/dice/doc.go000066400000000000000000000004631470323427300260720ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Dice is the "Roll the dice" application. // // [Getting Started] uses this example to demonstrate OpenTelemetry Go. // // [Getting Started]: https://opentelemetry.io/docs/languages/net/automatic/getting-started/ package main open-telemetry-opentelemetry-go-contrib-e5abccb/examples/dice/go.mod000066400000000000000000000021041470323427300260760ustar00rootroot00000000000000module go.opentelemetry.io/contrib/examples/dice go 1.22 require ( go.opentelemetry.io/contrib/bridges/otelslog v0.5.0 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.7.0 go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.31.0 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0 go.opentelemetry.io/otel/log v0.7.0 go.opentelemetry.io/otel/metric v1.31.0 go.opentelemetry.io/otel/sdk v1.31.0 go.opentelemetry.io/otel/sdk/log v0.7.0 go.opentelemetry.io/otel/sdk/metric v1.31.0 ) require ( github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect go.opentelemetry.io/otel/trace v1.31.0 // indirect golang.org/x/sys v0.26.0 // indirect ) replace ( go.opentelemetry.io/contrib/bridges/otelslog => ../../bridges/otelslog go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp => ../../instrumentation/net/http/otelhttp ) open-telemetry-opentelemetry-go-contrib-e5abccb/examples/dice/go.sum000066400000000000000000000071421470323427300261320ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.7.0 h1:TwmL3O3fRR80m8EshBrd8YydEZMcUCsZXzOUlnFohwM= go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.7.0/go.mod h1:tH98dDv5KPmPThswbXA0fr0Lwfs+OhK8HgaCo7PjRrk= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.31.0 h1:HZgBIps9wH0RDrwjrmNa3DVbNRW60HEhdzqZFyAp3fI= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.31.0/go.mod h1:RDRhvt6TDG0eIXmonAx5bd9IcwpqCkziwkOClzWKwAQ= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0 h1:UGZ1QwZWY67Z6BmckTU+9Rxn04m2bD3gD6Mk0OIOCPk= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0/go.mod h1:fcwWuDuaObkkChiDlhEpSq9+X1C0omv+s5mBtToAQ64= go.opentelemetry.io/otel/log v0.7.0 h1:d1abJc0b1QQZADKvfe9JqqrfmPYQCz2tUSO+0XZmuV4= go.opentelemetry.io/otel/log v0.7.0/go.mod h1:2jf2z7uVfnzDNknKTO9G+ahcOAyWcp1fJmk/wJjULRo= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/sdk/log v0.7.0 h1:dXkeI2S0MLc5g0/AwxTZv6EUEjctiH8aG14Am56NTmQ= go.opentelemetry.io/otel/sdk/log v0.7.0/go.mod h1:oIRXpW+WD6M8BuGj5rtS0aRu/86cbDV/dAfNaZBIjYM= go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc= go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= open-telemetry-opentelemetry-go-contrib-e5abccb/examples/dice/main.go000066400000000000000000000040141470323427300262450ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package main import ( "context" "errors" "log" "net" "net/http" "os" "os/signal" "time" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) func main() { if err := run(); err != nil { log.Fatalln(err) } } func run() (err error) { // Handle SIGINT (CTRL+C) gracefully. ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) defer stop() // Set up OpenTelemetry. otelShutdown, err := setupOTelSDK(ctx) if err != nil { return } // Handle shutdown properly so nothing leaks. defer func() { err = errors.Join(err, otelShutdown(context.Background())) }() // Start HTTP server. srv := &http.Server{ Addr: ":8080", BaseContext: func(_ net.Listener) context.Context { return ctx }, ReadTimeout: time.Second, WriteTimeout: 10 * time.Second, Handler: newHTTPHandler(), } srvErr := make(chan error, 1) go func() { srvErr <- srv.ListenAndServe() }() // Wait for interruption. select { case err = <-srvErr: // Error when starting HTTP server. return case <-ctx.Done(): // Wait for first CTRL+C. // Stop receiving signal notifications as soon as possible. stop() } // When Shutdown is called, ListenAndServe immediately returns ErrServerClosed. err = srv.Shutdown(context.Background()) return } func newHTTPHandler() http.Handler { mux := http.NewServeMux() // handleFunc is a replacement for mux.HandleFunc // which enriches the handler's HTTP instrumentation with the pattern as the http.route. handleFunc := func(pattern string, handlerFunc func(http.ResponseWriter, *http.Request)) { // Configure the "http.route" for the HTTP instrumentation. handler := otelhttp.WithRouteTag(pattern, http.HandlerFunc(handlerFunc)) mux.Handle(pattern, handler) } // Register handlers. handleFunc("/rolldice/", rolldice) handleFunc("/rolldice/{player}", rolldice) // Add HTTP instrumentation for the whole server. handler := otelhttp.NewHandler(mux, "/") return handler } open-telemetry-opentelemetry-go-contrib-e5abccb/examples/dice/otel.go000066400000000000000000000062561470323427300262760ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package main import ( "context" "errors" "time" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/stdout/stdoutlog" "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/log/global" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/sdk/log" "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/trace" ) // setupOTelSDK bootstraps the OpenTelemetry pipeline. // If it does not return an error, make sure to call shutdown for proper cleanup. func setupOTelSDK(ctx context.Context) (shutdown func(context.Context) error, err error) { var shutdownFuncs []func(context.Context) error // shutdown calls cleanup functions registered via shutdownFuncs. // The errors from the calls are joined. // Each registered cleanup will be invoked once. shutdown = func(ctx context.Context) error { var err error for _, fn := range shutdownFuncs { err = errors.Join(err, fn(ctx)) } shutdownFuncs = nil return err } // handleErr calls shutdown for cleanup and makes sure that all errors are returned. handleErr := func(inErr error) { err = errors.Join(inErr, shutdown(ctx)) } // Set up propagator. prop := newPropagator() otel.SetTextMapPropagator(prop) // Set up trace provider. tracerProvider, err := newTraceProvider() if err != nil { handleErr(err) return } shutdownFuncs = append(shutdownFuncs, tracerProvider.Shutdown) otel.SetTracerProvider(tracerProvider) // Set up meter provider. meterProvider, err := newMeterProvider() if err != nil { handleErr(err) return } shutdownFuncs = append(shutdownFuncs, meterProvider.Shutdown) otel.SetMeterProvider(meterProvider) // Set up logger provider. loggerProvider, err := newLoggerProvider() if err != nil { handleErr(err) return } shutdownFuncs = append(shutdownFuncs, loggerProvider.Shutdown) global.SetLoggerProvider(loggerProvider) return } func newPropagator() propagation.TextMapPropagator { return propagation.NewCompositeTextMapPropagator( propagation.TraceContext{}, propagation.Baggage{}, ) } func newTraceProvider() (*trace.TracerProvider, error) { traceExporter, err := stdouttrace.New( stdouttrace.WithPrettyPrint()) if err != nil { return nil, err } traceProvider := trace.NewTracerProvider( trace.WithBatcher(traceExporter, // Default is 5s. Set to 1s for demonstrative purposes. trace.WithBatchTimeout(time.Second)), ) return traceProvider, nil } func newMeterProvider() (*metric.MeterProvider, error) { metricExporter, err := stdoutmetric.New() if err != nil { return nil, err } meterProvider := metric.NewMeterProvider( metric.WithReader(metric.NewPeriodicReader(metricExporter, // Default is 1m. Set to 3s for demonstrative purposes. metric.WithInterval(3*time.Second))), ) return meterProvider, nil } func newLoggerProvider() (*log.LoggerProvider, error) { logExporter, err := stdoutlog.New() if err != nil { return nil, err } loggerProvider := log.NewLoggerProvider( log.WithProcessor(log.NewBatchProcessor(logExporter)), ) return loggerProvider, nil } open-telemetry-opentelemetry-go-contrib-e5abccb/examples/dice/rolldice.go000066400000000000000000000027531470323427300271260ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package main import ( "fmt" "io" "math/rand" "net/http" "strconv" "go.opentelemetry.io/contrib/bridges/otelslog" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" ) const name = "go.opentelemetry.io/contrib/examples/dice" var ( tracer = otel.Tracer(name) meter = otel.Meter(name) logger = otelslog.NewLogger(name) rollCnt metric.Int64Counter ) func init() { var err error rollCnt, err = meter.Int64Counter("dice.rolls", metric.WithDescription("The number of rolls by roll value"), metric.WithUnit("{roll}")) if err != nil { panic(err) } } func rolldice(w http.ResponseWriter, r *http.Request) { ctx, span := tracer.Start(r.Context(), "roll") defer span.End() roll := 1 + rand.Intn(6) //nolint:gosec // G404: Use of weak random number generator (math/rand instead of crypto/rand) is ignored as this is not security-sensitive. var msg string if player := r.PathValue("player"); player != "" { msg = fmt.Sprintf("%s is rolling the dice", player) } else { msg = "Anonymous player is rolling the dice" } logger.InfoContext(ctx, msg, "result", roll) rollValueAttr := attribute.Int("roll.value", roll) span.SetAttributes(rollValueAttr) rollCnt.Add(ctx, 1, metric.WithAttributes(rollValueAttr)) resp := strconv.Itoa(roll) + "\n" if _, err := io.WriteString(w, resp); err != nil { logger.ErrorContext(ctx, "Write failed", "error", err) } } open-telemetry-opentelemetry-go-contrib-e5abccb/examples/namedtracer/000077500000000000000000000000001470323427300263545ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/examples/namedtracer/foo/000077500000000000000000000000001470323427300271375ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/examples/namedtracer/foo/foo.go000066400000000000000000000015721470323427300302560ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package foo // import "go.opentelemetry.io/contrib/examples/namedtracer/foo" import ( "context" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" ) var lemonsKey = attribute.Key("ex.com/lemons") // SubOperation is an example to demonstrate the use of named tracer. // It creates a named tracer with its package path. func SubOperation(ctx context.Context) error { // Using global provider. Alternative is to have application provide a getter // for its component to get the instance of the provider. tr := otel.Tracer("go.opentelemetry.io/contrib/examples/namedtracer/foo") var span trace.Span _, span = tr.Start(ctx, "Sub operation...") defer span.End() span.SetAttributes(lemonsKey.String("five")) span.AddEvent("Sub span event") return nil } open-telemetry-opentelemetry-go-contrib-e5abccb/examples/namedtracer/go.mod000066400000000000000000000007341470323427300274660ustar00rootroot00000000000000module go.opentelemetry.io/contrib/examples/namedtracer go 1.22 require ( github.com/go-logr/stdr v1.2.2 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0 go.opentelemetry.io/otel/sdk v1.31.0 go.opentelemetry.io/otel/trace v1.31.0 ) require ( github.com/go-logr/logr v1.4.2 // indirect github.com/google/uuid v1.6.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect golang.org/x/sys v0.26.0 // indirect ) open-telemetry-opentelemetry-go-contrib-e5abccb/examples/namedtracer/go.sum000066400000000000000000000047141470323427300275150ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0 h1:UGZ1QwZWY67Z6BmckTU+9Rxn04m2bD3gD6Mk0OIOCPk= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0/go.mod h1:fcwWuDuaObkkChiDlhEpSq9+X1C0omv+s5mBtToAQ64= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= open-telemetry-opentelemetry-go-contrib-e5abccb/examples/namedtracer/main.go000066400000000000000000000036031470323427300276310ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package main import ( "context" "fmt" "log" "github.com/go-logr/stdr" "go.opentelemetry.io/contrib/examples/namedtracer/foo" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/baggage" "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" ) var ( fooKey = attribute.Key("ex.com/foo") barKey = attribute.Key("ex.com/bar") anotherKey = attribute.Key("ex.com/another") ) var tp *sdktrace.TracerProvider // initTracer creates and registers trace provider instance. func initTracer() error { exp, err := stdouttrace.New(stdouttrace.WithPrettyPrint()) if err != nil { return fmt.Errorf("failed to initialize stdouttrace exporter: %w", err) } bsp := sdktrace.NewBatchSpanProcessor(exp) tp = sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithSpanProcessor(bsp), ) otel.SetTracerProvider(tp) return nil } func main() { // Set logging level to info to see SDK status messages stdr.SetVerbosity(5) // initialize trace provider. if err := initTracer(); err != nil { log.Panic(err) } // Create a named tracer with package path as its name. tracer := tp.Tracer("go.opentelemetry.io/contrib/examples/namedtracer") ctx := context.Background() defer func() { _ = tp.Shutdown(ctx) }() m0, _ := baggage.NewMemberRaw(string(fooKey), "foo1") m1, _ := baggage.NewMemberRaw(string(barKey), "bar1") b, _ := baggage.New(m0, m1) ctx = baggage.ContextWithBaggage(ctx, b) var span trace.Span ctx, span = tracer.Start(ctx, "operation") defer span.End() span.AddEvent("Nice operation!", trace.WithAttributes(attribute.Int("bogons", 100))) span.SetAttributes(anotherKey.String("yes")) if err := foo.SubOperation(ctx); err != nil { panic(err) } } open-telemetry-opentelemetry-go-contrib-e5abccb/examples/opencensus/000077500000000000000000000000001470323427300262515ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/examples/opencensus/go.mod000066400000000000000000000013731470323427300273630ustar00rootroot00000000000000module go.opentelemetry.io/contrib/examples/opencensus go 1.22 require ( go.opencensus.io v0.24.0 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/bridge/opencensus v1.31.0 go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.31.0 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0 go.opentelemetry.io/otel/sdk v1.31.0 go.opentelemetry.io/otel/sdk/metric v1.31.0 ) require ( github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/google/uuid v1.6.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect go.opentelemetry.io/otel/trace v1.31.0 // indirect golang.org/x/sys v0.26.0 // indirect ) open-telemetry-opentelemetry-go-contrib-e5abccb/examples/opencensus/go.sum000066400000000000000000000300251470323427300274040ustar00rootroot00000000000000cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/bridge/opencensus v1.31.0 h1:YrCZ8NpdMTunNIzRnNoG3KjSLu0PNmRtgtQVJuCxkAQ= go.opentelemetry.io/otel/bridge/opencensus v1.31.0/go.mod h1:2yEkg7WRb15imAr0jfS4XDNd8LNe/hRES+kFezyO6LI= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.31.0 h1:HZgBIps9wH0RDrwjrmNa3DVbNRW60HEhdzqZFyAp3fI= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.31.0/go.mod h1:RDRhvt6TDG0eIXmonAx5bd9IcwpqCkziwkOClzWKwAQ= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0 h1:UGZ1QwZWY67Z6BmckTU+9Rxn04m2bD3gD6Mk0OIOCPk= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0/go.mod h1:fcwWuDuaObkkChiDlhEpSq9+X1C0omv+s5mBtToAQ64= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc= go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= open-telemetry-opentelemetry-go-contrib-e5abccb/examples/opencensus/main.go000066400000000000000000000113761470323427300275340ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package main import ( "context" "fmt" "log" "time" ocmetric "go.opencensus.io/metric" "go.opencensus.io/metric/metricdata" "go.opencensus.io/metric/metricproducer" "go.opencensus.io/stats" "go.opencensus.io/stats/view" "go.opencensus.io/tag" octrace "go.opencensus.io/trace" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/bridge/opencensus" "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/sdk/metric" sdktrace "go.opentelemetry.io/otel/sdk/trace" ) var ( // instrumenttype differentiates between our gauge and view metrics. keyType = tag.MustNewKey("instrumenttype") // Counts the number of lines read in from standard input. countMeasure = stats.Int64("test_count", "A count of something", stats.UnitDimensionless) countView = &view.View{ Name: "test_count", Measure: countMeasure, Description: "A count of something", Aggregation: view.Count(), TagKeys: []tag.Key{keyType}, } ) func main() { log.Println("Using OpenTelemetry stdout exporters.") traceExporter, err := stdouttrace.New(stdouttrace.WithPrettyPrint()) if err != nil { log.Fatal(fmt.Errorf("error creating trace exporter: %w", err)) } metricsExporter, err := stdoutmetric.New() if err != nil { log.Fatal(fmt.Errorf("error creating metric exporter: %w", err)) } tracing(traceExporter) if err := monitoring(metricsExporter); err != nil { log.Fatal(err) } } // tracing demonstrates overriding the OpenCensus DefaultTracer to send spans // to the OpenTelemetry exporter by calling OpenCensus APIs. func tracing(otExporter sdktrace.SpanExporter) { ctx := context.Background() log.Println("Configuring OpenCensus. Not Registering any OpenCensus exporters.") octrace.ApplyConfig(octrace.Config{DefaultSampler: octrace.AlwaysSample()}) tp := sdktrace.NewTracerProvider(sdktrace.WithBatcher(otExporter)) otel.SetTracerProvider(tp) log.Println("Installing the OpenCensus bridge to make OpenCensus libraries write spans using OpenTelemetry.") opencensus.InstallTraceBridge() tp.ForceFlush(ctx) log.Println("Creating OpenCensus span, which should be printed out using the OpenTelemetry stdouttrace exporter.\n-- It should have no parent, since it is the first span.") ctx, outerOCSpan := octrace.StartSpan(ctx, "OpenCensusOuterSpan") outerOCSpan.End() tp.ForceFlush(ctx) log.Println("Creating OpenTelemetry span\n-- It should have the OpenCensus span as a parent, since the OpenCensus span was written with using OpenTelemetry APIs.") tracer := tp.Tracer("go.opentelemetry.io/contrib/examples/opencensus") ctx, otspan := tracer.Start(ctx, "OpenTelemetrySpan") otspan.End() tp.ForceFlush(ctx) log.Println("Creating OpenCensus span, which should be printed out using the OpenTelemetry stdouttrace exporter.\n-- It should have the OpenTelemetry span as a parent, since it was written using OpenTelemetry APIs") _, innerOCSpan := octrace.StartSpan(ctx, "OpenCensusInnerSpan") innerOCSpan.End() tp.ForceFlush(ctx) } // monitoring demonstrates creating an IntervalReader using the OpenTelemetry // exporter to send metrics to the exporter by using either an OpenCensus // registry or an OpenCensus view. func monitoring(exporter metric.Exporter) error { log.Println("Adding the OpenCensus metric Producer to an OpenTelemetry Reader to export OpenCensus metrics using the OpenTelemetry stdout exporter.") // Register the OpenCensus metric Producer to add metrics from OpenCensus to the output. reader := metric.NewPeriodicReader(exporter, metric.WithProducer(opencensus.NewMetricProducer())) metric.NewMeterProvider(metric.WithReader(reader)) log.Println("Registering a gauge metric using an OpenCensus registry.") r := ocmetric.NewRegistry() metricproducer.GlobalManager().AddProducer(r) gauge, err := r.AddInt64Gauge( "test_gauge", ocmetric.WithDescription("A gauge for testing"), ocmetric.WithConstLabel(map[metricdata.LabelKey]metricdata.LabelValue{ {Key: keyType.Name()}: metricdata.NewLabelValue("gauge"), }), ) if err != nil { return fmt.Errorf("failed to add gauge: %w", err) } entry, err := gauge.GetEntry() if err != nil { return fmt.Errorf("failed to get gauge entry: %w", err) } log.Println("Registering a cumulative metric using an OpenCensus view.") if err := view.Register(countView); err != nil { return fmt.Errorf("failed to register views: %w", err) } ctx, err := tag.New(context.Background(), tag.Insert(keyType, "view")) if err != nil { return fmt.Errorf("failed to set tag: %w", err) } for i := int64(1); true; i++ { // update stats for our gauge entry.Set(i) // update stats for our view stats.Record(ctx, countMeasure.M(1)) time.Sleep(time.Second) } return nil } open-telemetry-opentelemetry-go-contrib-e5abccb/examples/otel-collector/000077500000000000000000000000001470323427300270165ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/examples/otel-collector/README.md000066400000000000000000000032051470323427300302750ustar00rootroot00000000000000# OpenTelemetry Collector Example This example illustrates how to export trace and metric data from the OpenTelemetry-Go SDK to the OpenTelemetry Collector. From there, we bring the trace data to Jaeger and the metric data to Prometheus The complete flow is: ``` -----> Jaeger (trace) App + SDK ---> OpenTelemetry Collector ---| -----> Prometheus (metrics) ``` # Prerequisites You will need [Docker Compose V2](https://docs.docker.com/compose/) installed for this demo. # Deploying to docker compose This command will bring up the OpenTelemetry Collector, Jaeger, and Prometheus, and expose the necessary ports for you to view the data. ```bash docker compose up -d ``` # Running the code You can find the complete code for this example in the [main.go](./main.go) file. To run it, ensure you have a somewhat recent version of Go (preferably >= 1.13) and do ```bash go run main.go ``` The example simulates an application, hard at work, computing for ten seconds then finishing. # Viewing instrumentation data Now the exciting part! Let's check out the telemetry data generated by our sample application ## Jaeger UI The Jaeger UI is available at [http://localhost:16686](http://localhost:16686). Navigate there in your favorite web-browser to view the generated traces. ## Prometheus The Prometheus UI is available at [http://localhost:9090](http://localhost:9090). Navigate there in your favorite web-browser to view the generated metrics, for instance, `testapp_run_total`. # Shutting down To shut down and clean the example, run ```bash docker compose down ``` open-telemetry-opentelemetry-go-contrib-e5abccb/examples/otel-collector/docker-compose.yaml000066400000000000000000000010351470323427300326130ustar00rootroot00000000000000# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 services: otel-collector: image: otel/opentelemetry-collector-contrib:0.111.0 command: ["--config=/etc/otel-collector.yaml"] volumes: - ./otel-collector.yaml:/etc/otel-collector.yaml ports: - 4317:4317 prometheus: image: prom/prometheus:v2.54.1 volumes: - ./prometheus.yaml:/etc/prometheus/prometheus.yml ports: - 9090:9090 jaeger: image: jaegertracing/all-in-one:1.60 ports: - 16686:16686 open-telemetry-opentelemetry-go-contrib-e5abccb/examples/otel-collector/go.mod000066400000000000000000000022361470323427300301270ustar00rootroot00000000000000module go.opentelemetry.io/contrib/examples/otel-collector go 1.22 require ( go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.31.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0 go.opentelemetry.io/otel/metric v1.31.0 go.opentelemetry.io/otel/sdk v1.31.0 go.opentelemetry.io/otel/sdk/metric v1.31.0 go.opentelemetry.io/otel/trace v1.31.0 google.golang.org/grpc v1.67.1 ) require ( github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect golang.org/x/net v0.30.0 // indirect golang.org/x/sys v0.26.0 // indirect golang.org/x/text v0.19.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 // indirect google.golang.org/protobuf v1.35.1 // indirect ) open-telemetry-opentelemetry-go-contrib-e5abccb/examples/otel-collector/go.sum000066400000000000000000000117111470323427300301520ustar00rootroot00000000000000github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.31.0 h1:FZ6ei8GFW7kyPYdxJaV2rgI6M+4tvZzhYsQ2wgyVC08= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.31.0/go.mod h1:MdEu/mC6j3D+tTEfvI15b5Ci2Fn7NneJ71YMoiS3tpI= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 h1:K0XaT3DwHAcV4nKLzcQvwAgSyisUghWoY20I7huthMk= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0/go.mod h1:B5Ki776z/MBnVha1Nzwp5arlzBbE3+1jk+pGmaP5HME= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0 h1:FFeLy03iVTXP6ffeN2iXrxfGsZGCjVx0/4KlizjyBwU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0/go.mod h1:TMu73/k1CP8nBUpDLc71Wj/Kf7ZS9FK5b53VapRsP9o= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc= go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 h1:T6rh4haD3GVYsgEfWExoCZA2o2FmbNyKpTuAxbEFPTg= google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:wp2WsuBYj6j8wUdo3ToZsdxxixbvQNAHqVJrTgi5E5M= google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 h1:QCqS/PdaHTSWGvupk2F/ehwHtGc0/GYkT+3GAcR1CCc= google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= open-telemetry-opentelemetry-go-contrib-e5abccb/examples/otel-collector/main.go000066400000000000000000000117051470323427300302750ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Example using OTLP exporters + collector + third-party backends. For // information about using the exporter, see: // https://pkg.go.dev/go.opentelemetry.io/otel/exporters/otlp?tab=doc#example-package-Insecure package main import ( "context" "fmt" "log" "os" "os/signal" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/propagation" sdkmetric "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.26.0" "go.opentelemetry.io/otel/trace" ) var serviceName = semconv.ServiceNameKey.String("test-service") // Initialize a gRPC connection to be used by both the tracer and meter // providers. func initConn() (*grpc.ClientConn, error) { // It connects the OpenTelemetry Collector through local gRPC connection. // You may replace `localhost:4317` with your endpoint. conn, err := grpc.NewClient("localhost:4317", // Note the use of insecure transport here. TLS is recommended in production. grpc.WithTransportCredentials(insecure.NewCredentials()), ) if err != nil { return nil, fmt.Errorf("failed to create gRPC connection to collector: %w", err) } return conn, err } // Initializes an OTLP exporter, and configures the corresponding trace provider. func initTracerProvider(ctx context.Context, res *resource.Resource, conn *grpc.ClientConn) (func(context.Context) error, error) { // Set up a trace exporter traceExporter, err := otlptracegrpc.New(ctx, otlptracegrpc.WithGRPCConn(conn)) if err != nil { return nil, fmt.Errorf("failed to create trace exporter: %w", err) } // Register the trace exporter with a TracerProvider, using a batch // span processor to aggregate spans before export. bsp := sdktrace.NewBatchSpanProcessor(traceExporter) tracerProvider := sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithResource(res), sdktrace.WithSpanProcessor(bsp), ) otel.SetTracerProvider(tracerProvider) // Set global propagator to tracecontext (the default is no-op). otel.SetTextMapPropagator(propagation.TraceContext{}) // Shutdown will flush any remaining spans and shut down the exporter. return tracerProvider.Shutdown, nil } // Initializes an OTLP exporter, and configures the corresponding meter provider. func initMeterProvider(ctx context.Context, res *resource.Resource, conn *grpc.ClientConn) (func(context.Context) error, error) { metricExporter, err := otlpmetricgrpc.New(ctx, otlpmetricgrpc.WithGRPCConn(conn)) if err != nil { return nil, fmt.Errorf("failed to create metrics exporter: %w", err) } meterProvider := sdkmetric.NewMeterProvider( sdkmetric.WithReader(sdkmetric.NewPeriodicReader(metricExporter)), sdkmetric.WithResource(res), ) otel.SetMeterProvider(meterProvider) return meterProvider.Shutdown, nil } func main() { log.Printf("Waiting for connection...") ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) defer cancel() conn, err := initConn() if err != nil { log.Fatal(err) } res, err := resource.New(ctx, resource.WithAttributes( // The service name used to display traces in backends serviceName, ), ) if err != nil { log.Fatal(err) } shutdownTracerProvider, err := initTracerProvider(ctx, res, conn) if err != nil { log.Fatal(err) } defer func() { if err := shutdownTracerProvider(ctx); err != nil { log.Fatalf("failed to shutdown TracerProvider: %s", err) } }() shutdownMeterProvider, err := initMeterProvider(ctx, res, conn) if err != nil { log.Fatal(err) } defer func() { if err := shutdownMeterProvider(ctx); err != nil { log.Fatalf("failed to shutdown MeterProvider: %s", err) } }() name := "go.opentelemetry.io/contrib/examples/otel-collector" tracer := otel.Tracer(name) meter := otel.Meter(name) // Attributes represent additional key-value descriptors that can be bound // to a metric observer or recorder. commonAttrs := []attribute.KeyValue{ attribute.String("attrA", "chocolate"), attribute.String("attrB", "raspberry"), attribute.String("attrC", "vanilla"), } runCount, err := meter.Int64Counter("run", metric.WithDescription("The number of times the iteration ran")) if err != nil { log.Fatal(err) } // Work begins ctx, span := tracer.Start( ctx, "CollectorExporter-Example", trace.WithAttributes(commonAttrs...)) defer span.End() for i := 0; i < 10; i++ { _, iSpan := tracer.Start(ctx, fmt.Sprintf("Sample-%d", i)) runCount.Add(ctx, 1, metric.WithAttributes(commonAttrs...)) log.Printf("Doing really hard work (%d / 10)\n", i+1) <-time.After(time.Second) iSpan.End() } log.Printf("Done!") } open-telemetry-opentelemetry-go-contrib-e5abccb/examples/otel-collector/otel-collector.yaml000066400000000000000000000011051470323427300326260ustar00rootroot00000000000000# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 receivers: otlp: protocols: grpc: endpoint: 0.0.0.0:4317 processors: extensions: health_check: {} exporters: otlp: endpoint: jaeger:4317 tls: insecure: true prometheus: endpoint: 0.0.0.0:9090 namespace: testapp debug: service: extensions: [health_check] pipelines: traces: receivers: [otlp] processors: [] exporters: [otlp, debug] metrics: receivers: [otlp] processors: [] exporters: [prometheus, debug] open-telemetry-opentelemetry-go-contrib-e5abccb/examples/otel-collector/prometheus.yaml000066400000000000000000000003211470323427300320710ustar00rootroot00000000000000# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 scrape_configs: - job_name: 'otel-collector' scrape_interval: 5s static_configs: - targets: ['otel-collector:9090'] open-telemetry-opentelemetry-go-contrib-e5abccb/examples/passthrough/000077500000000000000000000000001470323427300264365ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/examples/passthrough/README.md000066400000000000000000000030251470323427300277150ustar00rootroot00000000000000# "Passthrough" setup for OpenTelemetry Some Go programs may wish to propagate context without recording spans. To do this in OpenTelemetry, simply install `TextMapPropagators`, but do not install a TracerProvider using the SDK. This works because the default TracerProvider implementation returns a "Non-Recording" span that keeps the context of the caller but does not record spans. For example, when you initialize your global settings, the following will propagate context without recording spans: ```golang // Setup Propagators only otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) ``` But the following will propagate context _and_ create new, potentially recorded spans: ```golang // Setup SDK exp, _ := stdout.New(stdout.WithPrettyPrint()) tp = sdktrace.NewTracerProvider( sdktrace.WithBatcher(exp), ) otel.SetTracerProvider(tp) // Setup Propagators otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) ``` ## The Demo The demo has the following call structure: `Outer -> Passthrough -> Inner` If all components had both an SDK and propagators registered, we would expect the trace to look like: ``` |-------outer---------| |-Passthrough recv-| |Passthrough send| |---inner---| ``` However, in this demo, only the outer and inner have TracerProvider backed by the SDK. All components have Propagators set. In this case, we expect to see: ``` |-------outer---------| |---inner---| ``` open-telemetry-opentelemetry-go-contrib-e5abccb/examples/passthrough/go.mod000066400000000000000000000007501470323427300275460ustar00rootroot00000000000000module go.opentelemetry.io/contrib/examples/passthrough go 1.22 require ( go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0 go.opentelemetry.io/otel/sdk v1.31.0 go.opentelemetry.io/otel/trace v1.31.0 ) require ( github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect golang.org/x/sys v0.26.0 // indirect ) open-telemetry-opentelemetry-go-contrib-e5abccb/examples/passthrough/go.sum000066400000000000000000000047141470323427300275770ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0 h1:UGZ1QwZWY67Z6BmckTU+9Rxn04m2bD3gD6Mk0OIOCPk= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0/go.mod h1:fcwWuDuaObkkChiDlhEpSq9+X1C0omv+s5mBtToAQ64= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= open-telemetry-opentelemetry-go-contrib-e5abccb/examples/passthrough/handler/000077500000000000000000000000001470323427300300535ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/examples/passthrough/handler/handler.go000066400000000000000000000042651470323427300320260ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package handler // import "go.opentelemetry.io/contrib/examples/passthrough/handler" import ( "context" "log" "net/http" "time" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" ) // Handler is a minimal implementation of the handler and client from // go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp for demonstration purposes. // It handles an incoming http request, and makes an outgoing http request. type Handler struct { propagators propagation.TextMapPropagator tracer trace.Tracer next func(r *http.Request) } // New returns a new Handler that will trace requests before handing them off // to next. func New(next func(r *http.Request)) *Handler { // Like most instrumentation packages, this handler defaults to using the // global propagators and tracer providers. return &Handler{ propagators: otel.GetTextMapPropagator(), tracer: otel.Tracer("go.opentelemetry.io/contrib/examples/passthrough/handler"), next: next, } } // HandleHTTPReq mimics what an instrumented http server does. func (h *Handler) HandleHTTPReq(r *http.Request) { ctx := h.propagators.Extract(r.Context(), propagation.HeaderCarrier(r.Header)) var span trace.Span log.Println("The \"handle passthrough request\" span should NOT be recorded, because it is recorded by a TracerProvider not backed by the SDK.") ctx, span = h.tracer.Start(ctx, "handle passthrough request") defer span.End() // Pretend to do work time.Sleep(time.Second) h.makeOutgoingRequest(ctx) } // makeOutgoingRequest mimics what an instrumented http client does. func (h *Handler) makeOutgoingRequest(ctx context.Context) { // make a new http request r, err := http.NewRequest("", "", nil) if err != nil { panic(err) } log.Println("The \"make outgoing request from passthrough\" span should NOT be recorded, because it is recorded by a TracerProvider not backed by the SDK.") ctx, span := h.tracer.Start(ctx, "make outgoing request from passthrough") defer span.End() r = r.WithContext(ctx) h.propagators.Inject(ctx, propagation.HeaderCarrier(r.Header)) h.next(r) } open-telemetry-opentelemetry-go-contrib-e5abccb/examples/passthrough/main.go000066400000000000000000000056721470323427300277230ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package main import ( "context" "fmt" "log" "net/http" "time" "go.opentelemetry.io/contrib/examples/passthrough/handler" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/propagation" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" ) const name = "go.opentelemetry.io/contrib/examples/passthrough" func main() { ctx := context.Background() initPassthroughGlobals() tp, err := nonGlobalTracer() if err != nil { log.Fatal(err) } defer func() { _ = tp.Shutdown(ctx) }() // make an initial http request r, err := http.NewRequest("", "", nil) if err != nil { panic(err) } // This is roughly what an instrumented http client does. log.Println("The \"make outer request\" span should be recorded, because it is recorded with a Tracer from the SDK TracerProvider") var span trace.Span tracer := tp.Tracer(name) ctx, span = tracer.Start(ctx, "make outer request") defer span.End() r = r.WithContext(ctx) otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(r.Header)) backendFunc := func(r *http.Request) { // This is roughly what an instrumented http server does. ctx := r.Context() tp := trace.SpanFromContext(ctx).TracerProvider() tracer := tp.Tracer(name) ctx = otel.GetTextMapPropagator().Extract(ctx, propagation.HeaderCarrier(r.Header)) log.Println("The \"handle inner request\" span should be recorded, because it is recorded with a Tracer from the SDK TracerProvider") _, span := tracer.Start(ctx, "handle inner request") defer span.End() // Do "backend work" time.Sleep(time.Second) } // This handler will be a passthrough, since we didn't set a global TracerProvider passthroughHandler := handler.New(backendFunc) passthroughHandler.HandleHTTPReq(r) } func initPassthroughGlobals() { // We explicitly DO NOT set the global TracerProvider using otel.SetTracerProvider(). // The unset TracerProvider returns a "non-recording" span, but still passes through context. log.Println("Register a global TextMapPropagator, but do not register a global TracerProvider to be in \"passthrough\" mode.") log.Println("The \"passthrough\" mode propagates the TraceContext and Baggage, but does not record spans.") otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) } // nonGlobalTracer creates a trace provider instance for testing, but doesn't // set it as the global tracer provider. func nonGlobalTracer() (*sdktrace.TracerProvider, error) { exp, err := stdouttrace.New(stdouttrace.WithPrettyPrint()) if err != nil { return nil, fmt.Errorf("failed to initialize stdouttrace exporter: %w", err) } bsp := sdktrace.NewBatchSpanProcessor(exp) tp := sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithSpanProcessor(bsp), ) return tp, nil } open-telemetry-opentelemetry-go-contrib-e5abccb/examples/prometheus/000077500000000000000000000000001470323427300262625ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/examples/prometheus/doc.go000066400000000000000000000002371470323427300273600ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package main provides a code sample of the Prometheus exporter. package main open-telemetry-opentelemetry-go-contrib-e5abccb/examples/prometheus/go.mod000066400000000000000000000017561470323427300274010ustar00rootroot00000000000000module go.opentelemetry.io/contrib/examples/prometheus go 1.22 require ( github.com/prometheus/client_golang v1.20.4 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/exporters/prometheus v0.53.0 go.opentelemetry.io/otel/metric v1.31.0 go.opentelemetry.io/otel/sdk/metric v1.31.0 ) require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/klauspost/compress v1.17.11 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.60.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect go.opentelemetry.io/otel/sdk v1.31.0 // indirect go.opentelemetry.io/otel/trace v1.31.0 // indirect golang.org/x/sys v0.26.0 // indirect google.golang.org/protobuf v1.35.1 // indirect ) open-telemetry-opentelemetry-go-contrib-e5abccb/examples/prometheus/go.sum000066400000000000000000000106451470323427300274230ustar00rootroot00000000000000github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI= github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.60.0 h1:+V9PAREWNvJMAuJ1x1BaWl9dewMW4YrHZQbx0sJNllA= github.com/prometheus/common v0.60.0/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/exporters/prometheus v0.53.0 h1:QXobPHrwiGLM4ufrY3EOmDPJpo2P90UuFau4CDPJA/I= go.opentelemetry.io/otel/exporters/prometheus v0.53.0/go.mod h1:WOAXGr3D00CfzmFxtTV1eR0GpoHuPEu+HJT8UWW2SIU= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc= go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= open-telemetry-opentelemetry-go-contrib-e5abccb/examples/prometheus/main.go000066400000000000000000000051211470323427300275340ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package main import ( "context" "fmt" "log" "math/rand" "net/http" "os" "os/signal" "time" "github.com/prometheus/client_golang/prometheus/promhttp" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/exporters/prometheus" api "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/sdk/metric" ) const meterName = "go.opentelemetry.io/contrib/examples/prometheus" func main() { rng := rand.New(rand.NewSource(time.Now().UnixNano())) //nolint:gosec // G404: Use of weak random number generator (math/rand instead of crypto/rand) is ignored as this is not security-sensitive. ctx := context.Background() // The exporter embeds a default OpenTelemetry Reader and // implements prometheus.Collector, allowing it to be used as // both a Reader and Collector. exporter, err := prometheus.New() if err != nil { log.Fatal(err) } provider := metric.NewMeterProvider(metric.WithReader(exporter)) meter := provider.Meter(meterName) // Start the prometheus HTTP server and pass the exporter Collector to it go serveMetrics() opt := api.WithAttributes( attribute.Key("A").String("B"), attribute.Key("C").String("D"), ) // This is the equivalent of prometheus.NewCounterVec counter, err := meter.Float64Counter("foo", api.WithDescription("a simple counter")) if err != nil { log.Fatal(err) } counter.Add(ctx, 5, opt) gauge, err := meter.Float64ObservableGauge("bar", api.WithDescription("a fun little gauge")) if err != nil { log.Fatal(err) } _, err = meter.RegisterCallback(func(_ context.Context, o api.Observer) error { n := -10. + rng.Float64()*(90.) // [-10, 100) o.ObserveFloat64(gauge, n, opt) return nil }, gauge) if err != nil { log.Fatal(err) } // This is the equivalent of prometheus.NewHistogramVec histogram, err := meter.Float64Histogram( "baz", api.WithDescription("a histogram with custom buckets and rename"), api.WithExplicitBucketBoundaries(64, 128, 256, 512, 1024, 2048, 4096), ) if err != nil { log.Fatal(err) } histogram.Record(ctx, 136, opt) histogram.Record(ctx, 64, opt) histogram.Record(ctx, 701, opt) histogram.Record(ctx, 830, opt) ctx, _ = signal.NotifyContext(ctx, os.Interrupt) <-ctx.Done() } func serveMetrics() { log.Printf("serving metrics at localhost:2223/metrics") http.Handle("/metrics", promhttp.Handler()) err := http.ListenAndServe(":2223", nil) //nolint:gosec // Ignoring G114: Use of net/http serve function that has no support for setting timeouts. if err != nil { fmt.Printf("error serving http: %v", err) return } } open-telemetry-opentelemetry-go-contrib-e5abccb/examples/zipkin/000077500000000000000000000000001470323427300253735ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/examples/zipkin/Dockerfile000066400000000000000000000004301470323427300273620ustar00rootroot00000000000000# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 FROM golang:1.23-alpine COPY . /go/src/github.com/open-telemetry/opentelemetry-go/ WORKDIR /go/src/github.com/open-telemetry/opentelemetry-go/example/zipkin/ RUN go install ./main.go CMD ["/go/bin/main"] open-telemetry-opentelemetry-go-contrib-e5abccb/examples/zipkin/README.md000066400000000000000000000017211470323427300266530ustar00rootroot00000000000000# Zipkin Exporter Example Send an example span to a [Zipkin](https://zipkin.io/) service. These instructions expect you have [docker-compose](https://docs.docker.com/compose/) installed. Bring up the `zipkin-collector` service and example `zipkin-client` service to send an example trace: ```sh docker-compose up --detach zipkin-collector zipkin-client ``` The `zipkin-client` service sends just one trace and exits. Retrieve the `traceId` generated by the `zipkin-client` service; should be the last line in the logs: ```sh docker-compose logs --tail=1 zipkin-client ``` With the `traceId` you can view the trace from the `zipkin-collector` service UI hosted on port `9411`, e.g. with `traceId` of `f5695ba3b2ed00ea583fa4fa0badbeef`: [http://localhost:9411/zipkin/traces/f5695ba3b2ed00ea583fa4fa0badbeef](http://localhost:9411/zipkin/traces/f5695ba3b2ed00ea583fa4fa0badbeef) Shut down the services when you are finished with the example: ```sh docker-compose down ``` open-telemetry-opentelemetry-go-contrib-e5abccb/examples/zipkin/docker-compose.yml000066400000000000000000000012051470323427300310260ustar00rootroot00000000000000# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 version: "3.7" services: zipkin-collector: image: openzipkin/zipkin-slim:latest ports: - "9411:9411" networks: - example zipkin-client: build: dockerfile: $PWD/Dockerfile context: ../.. command: - "/bin/sh" - "-c" - "while ! nc -w 1 -z zipkin-collector 9411; do echo sleep for 1s waiting for zipkin-collector to become available; sleep 1; done && /go/bin/main -zipkin http://zipkin-collector:9411/api/v2/spans" networks: - example depends_on: - zipkin-collector networks: example: open-telemetry-opentelemetry-go-contrib-e5abccb/examples/zipkin/go.mod000066400000000000000000000010131470323427300264740ustar00rootroot00000000000000module go.opentelemetry.io/contrib/examples/zipkin go 1.22 require ( go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/exporters/zipkin v1.31.0 go.opentelemetry.io/otel/sdk v1.31.0 go.opentelemetry.io/otel/trace v1.31.0 ) require ( github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/openzipkin/zipkin-go v0.4.3 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect golang.org/x/sys v0.26.0 // indirect ) open-telemetry-opentelemetry-go-contrib-e5abccb/examples/zipkin/go.sum000066400000000000000000000051511470323427300265300ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/openzipkin/zipkin-go v0.4.3 h1:9EGwpqkgnwdEIJ+Od7QVSEIH+ocmm5nPat0G7sjsSdg= github.com/openzipkin/zipkin-go v0.4.3/go.mod h1:M9wCJZFWCo2RiY+o1eBCEMe0Dp2S5LDHcMZmk3RmK7c= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/exporters/zipkin v1.31.0 h1:CgucL0tj3717DJnni7HVVB2wExzi8c2zJNEA2BhLMvI= go.opentelemetry.io/otel/exporters/zipkin v1.31.0/go.mod h1:rfzOVNiSwIcWtEC2J8epwG26fiaXlYvLySJ7bwsrtAE= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= open-telemetry-opentelemetry-go-contrib-e5abccb/examples/zipkin/main.go000066400000000000000000000043431470323427300266520ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Command zipkin is an example program that creates spans // and uploads to openzipkin collector. package main import ( "context" "flag" "log" "os" "os/signal" "time" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/zipkin" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.26.0" "go.opentelemetry.io/otel/trace" ) const name = "go.opentelemetry.io/contrib/examples/zipkin" var logger = log.New(os.Stderr, "zipkin-example", log.Ldate|log.Ltime|log.Llongfile) // initTracer creates a new trace provider instance and registers it as global trace provider. func initTracer(url string) (func(context.Context) error, error) { // Create Zipkin Exporter and install it as a global tracer. // // For demoing purposes, always sample. In a production application, you should // configure the sampler to a trace.ParentBased(trace.TraceIDRatioBased) set at the desired // ratio. exporter, err := zipkin.New( url, zipkin.WithLogger(logger), ) if err != nil { return nil, err } batcher := sdktrace.NewBatchSpanProcessor(exporter) tp := sdktrace.NewTracerProvider( sdktrace.WithSpanProcessor(batcher), sdktrace.WithResource(resource.NewWithAttributes( semconv.SchemaURL, semconv.ServiceName("zipkin-test"), )), ) otel.SetTracerProvider(tp) return tp.Shutdown, nil } func main() { url := flag.String("zipkin", "http://localhost:9411/api/v2/spans", "zipkin url") flag.Parse() ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) defer cancel() shutdown, err := initTracer(*url) if err != nil { log.Fatal(err) } defer func() { if err := shutdown(ctx); err != nil { log.Fatal("failed to shutdown TracerProvider: %w", err) } }() tr := otel.GetTracerProvider().Tracer(name) ctx, span := tr.Start(ctx, "foo", trace.WithSpanKind(trace.SpanKindServer)) <-time.After(6 * time.Millisecond) bar(ctx) <-time.After(6 * time.Millisecond) span.End() } func bar(ctx context.Context) { tr := trace.SpanFromContext(ctx).TracerProvider().Tracer(name) _, span := tr.Start(ctx, "bar") <-time.After(6 * time.Millisecond) span.End() } open-telemetry-opentelemetry-go-contrib-e5abccb/exporters/000077500000000000000000000000001470323427300243045ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/exporters/autoexport/000077500000000000000000000000001470323427300265165ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/exporters/autoexport/doc.go000066400000000000000000000005131470323427300276110ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package autoexport provides OpenTelemetry exporter factory functions // with defaults and environment variable support as defined by the // OpenTelemetry specification. package autoexport // import "go.opentelemetry.io/contrib/exporters/autoexport" open-telemetry-opentelemetry-go-contrib-e5abccb/exporters/autoexport/go.mod000066400000000000000000000047721470323427300276360ustar00rootroot00000000000000module go.opentelemetry.io/contrib/exporters/autoexport go 1.22 require ( github.com/prometheus/client_golang v1.20.4 github.com/stretchr/testify v1.9.0 go.opentelemetry.io/contrib/bridges/prometheus v0.56.0 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.7.0 go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.7.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.31.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.31.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 go.opentelemetry.io/otel/exporters/prometheus v0.53.0 go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.7.0 go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.31.0 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0 go.opentelemetry.io/otel/sdk v1.31.0 go.opentelemetry.io/otel/sdk/log v0.7.0 go.opentelemetry.io/otel/sdk/metric v1.31.0 go.opentelemetry.io/proto/otlp v1.3.1 go.uber.org/goleak v1.3.0 google.golang.org/protobuf v1.35.1 ) require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect github.com/klauspost/compress v1.17.11 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.60.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect go.opentelemetry.io/otel/log v0.7.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect go.opentelemetry.io/otel/trace v1.31.0 // indirect golang.org/x/net v0.30.0 // indirect golang.org/x/sys v0.26.0 // indirect golang.org/x/text v0.19.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 // indirect google.golang.org/grpc v1.67.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) replace go.opentelemetry.io/contrib/bridges/prometheus => ../../bridges/prometheus open-telemetry-opentelemetry-go-contrib-e5abccb/exporters/autoexport/go.sum000066400000000000000000000230361470323427300276550ustar00rootroot00000000000000github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI= github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.60.0 h1:+V9PAREWNvJMAuJ1x1BaWl9dewMW4YrHZQbx0sJNllA= github.com/prometheus/common v0.60.0/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.7.0 h1:iNba3cIZTDPB2+IAbVY/3TUN+pCCLrNYo2GaGtsKBak= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.7.0/go.mod h1:l5BDPiZ9FbeejzWTAX6BowMzQOM/GeaUQ6lr3sOcSkc= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.7.0 h1:mMOmtYie9Fx6TSVzw4W+NTpvoaS1JWWga37oI1a/4qQ= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.7.0/go.mod h1:yy7nDsMMBUkD+jeekJ36ur5f3jJIrmCwUrY67VFhNpA= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.31.0 h1:FZ6ei8GFW7kyPYdxJaV2rgI6M+4tvZzhYsQ2wgyVC08= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.31.0/go.mod h1:MdEu/mC6j3D+tTEfvI15b5Ci2Fn7NneJ71YMoiS3tpI= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.31.0 h1:ZsXq73BERAiNuuFXYqP4MR5hBrjXfMGSO+Cx7qoOZiM= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.31.0/go.mod h1:hg1zaDMpyZJuUzjFxFsRYBoccE86tM9Uf4IqNMUxvrY= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 h1:K0XaT3DwHAcV4nKLzcQvwAgSyisUghWoY20I7huthMk= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0/go.mod h1:B5Ki776z/MBnVha1Nzwp5arlzBbE3+1jk+pGmaP5HME= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0 h1:FFeLy03iVTXP6ffeN2iXrxfGsZGCjVx0/4KlizjyBwU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0/go.mod h1:TMu73/k1CP8nBUpDLc71Wj/Kf7ZS9FK5b53VapRsP9o= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 h1:lUsI2TYsQw2r1IASwoROaCnjdj2cvC2+Jbxvk6nHnWU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0/go.mod h1:2HpZxxQurfGxJlJDblybejHB6RX6pmExPNe517hREw4= go.opentelemetry.io/otel/exporters/prometheus v0.53.0 h1:QXobPHrwiGLM4ufrY3EOmDPJpo2P90UuFau4CDPJA/I= go.opentelemetry.io/otel/exporters/prometheus v0.53.0/go.mod h1:WOAXGr3D00CfzmFxtTV1eR0GpoHuPEu+HJT8UWW2SIU= go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.7.0 h1:TwmL3O3fRR80m8EshBrd8YydEZMcUCsZXzOUlnFohwM= go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.7.0/go.mod h1:tH98dDv5KPmPThswbXA0fr0Lwfs+OhK8HgaCo7PjRrk= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.31.0 h1:HZgBIps9wH0RDrwjrmNa3DVbNRW60HEhdzqZFyAp3fI= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.31.0/go.mod h1:RDRhvt6TDG0eIXmonAx5bd9IcwpqCkziwkOClzWKwAQ= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0 h1:UGZ1QwZWY67Z6BmckTU+9Rxn04m2bD3gD6Mk0OIOCPk= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0/go.mod h1:fcwWuDuaObkkChiDlhEpSq9+X1C0omv+s5mBtToAQ64= go.opentelemetry.io/otel/log v0.7.0 h1:d1abJc0b1QQZADKvfe9JqqrfmPYQCz2tUSO+0XZmuV4= go.opentelemetry.io/otel/log v0.7.0/go.mod h1:2jf2z7uVfnzDNknKTO9G+ahcOAyWcp1fJmk/wJjULRo= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/sdk/log v0.7.0 h1:dXkeI2S0MLc5g0/AwxTZv6EUEjctiH8aG14Am56NTmQ= go.opentelemetry.io/otel/sdk/log v0.7.0/go.mod h1:oIRXpW+WD6M8BuGj5rtS0aRu/86cbDV/dAfNaZBIjYM= go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc= go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 h1:T6rh4haD3GVYsgEfWExoCZA2o2FmbNyKpTuAxbEFPTg= google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:wp2WsuBYj6j8wUdo3ToZsdxxixbvQNAHqVJrTgi5E5M= google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 h1:QCqS/PdaHTSWGvupk2F/ehwHtGc0/GYkT+3GAcR1CCc= google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= open-telemetry-opentelemetry-go-contrib-e5abccb/exporters/autoexport/logs.go000066400000000000000000000062251470323427300300160ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package autoexport // import "go.opentelemetry.io/contrib/exporters/autoexport" import ( "context" "os" "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc" "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp" "go.opentelemetry.io/otel/exporters/stdout/stdoutlog" "go.opentelemetry.io/otel/sdk/log" ) const otelExporterOTLPLogsProtoEnvKey = "OTEL_EXPORTER_OTLP_LOGS_PROTOCOL" // LogOption applies an autoexport configuration option. type LogOption = option[log.Exporter] var logsSignal = newSignal[log.Exporter]("OTEL_LOGS_EXPORTER") // NewLogExporter returns a configured [go.opentelemetry.io/otel/sdk/log.Exporter] // defined using the environment variables described below. // // OTEL_LOGS_EXPORTER defines the logs exporter; supported values: // - "none" - "no operation" exporter // - "otlp" (default) - OTLP exporter; see [go.opentelemetry.io/otel/exporters/otlp/otlplog] // - "console" - Standard output exporter; see [go.opentelemetry.io/otel/exporters/stdout/stdoutlog] // // OTEL_EXPORTER_OTLP_PROTOCOL defines OTLP exporter's transport protocol; // supported values: // - "http/protobuf" (default) - protobuf-encoded data over HTTP connection; // see: [go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp] // - "grpc" - gRPC with protobuf-encoded data over HTTP/2 connection; // see: [go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc] // // OTEL_EXPORTER_OTLP_LOGS_PROTOCOL defines OTLP exporter's transport protocol for the logs signal; // supported values are the same as OTEL_EXPORTER_OTLP_PROTOCOL. // // An error is returned if an environment value is set to an unhandled value. // // Use [RegisterLogExporter] to handle more values of OTEL_LOGS_EXPORTER. // // Use [WithFallbackLogExporter] option to change the returned exporter // when OTEL_LOGS_EXPORTER is unset or empty. // // Use [IsNoneLogExporter] to check if the returned exporter is a "no operation" exporter. func NewLogExporter(ctx context.Context, opts ...LogOption) (log.Exporter, error) { return logsSignal.create(ctx, opts...) } // RegisterLogExporter sets the log.Exporter factory to be used when the // OTEL_LOGS_EXPORTER environment variable contains the exporter name. // This will panic if name has already been registered. func RegisterLogExporter(name string, factory func(context.Context) (log.Exporter, error)) { must(logsSignal.registry.store(name, factory)) } func init() { RegisterLogExporter("otlp", func(ctx context.Context) (log.Exporter, error) { proto := os.Getenv(otelExporterOTLPLogsProtoEnvKey) if proto == "" { proto = os.Getenv(otelExporterOTLPProtoEnvKey) } // Fallback to default, http/protobuf. if proto == "" { proto = "http/protobuf" } switch proto { case "grpc": return otlploggrpc.New(ctx) case "http/protobuf": return otlploghttp.New(ctx) default: return nil, errInvalidOTLPProtocol } }) RegisterLogExporter("console", func(ctx context.Context) (log.Exporter, error) { return stdoutlog.New() }) RegisterLogExporter("none", func(ctx context.Context) (log.Exporter, error) { return noopLogExporter{}, nil }) } open-telemetry-opentelemetry-go-contrib-e5abccb/exporters/autoexport/logs_test.go000066400000000000000000000061671470323427300310620ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package autoexport // import "go.opentelemetry.io/contrib/exporters/autoexport" import ( "context" "fmt" "reflect" "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/exporters/stdout/stdoutlog" "go.opentelemetry.io/otel/sdk/log" ) func TestLogExporterNone(t *testing.T) { t.Setenv("OTEL_LOGS_EXPORTER", "none") got, err := NewLogExporter(context.Background()) assert.NoError(t, err) t.Cleanup(func() { assert.NoError(t, got.ForceFlush(context.Background())) assert.NoError(t, got.Shutdown(context.Background())) }) assert.NoError(t, got.Export(context.Background(), nil)) assert.True(t, IsNoneLogExporter(got)) } func TestLogExporterConsole(t *testing.T) { t.Setenv("OTEL_LOGS_EXPORTER", "console") got, err := NewLogExporter(context.Background()) assert.NoError(t, err) assert.IsType(t, &stdoutlog.Exporter{}, got) } func TestLogExporterOTLP(t *testing.T) { t.Setenv("OTEL_LOGS_EXPORTER", "otlp") for _, tc := range []struct { protocol, clientType string }{ {"http/protobuf", "atomic.Pointer[go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp.client]"}, {"grpc", "otlploggrpc.logClient"}, {"", "atomic.Pointer[go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp.client]"}, } { t.Run(fmt.Sprintf("protocol=%q", tc.protocol), func(t *testing.T) { t.Setenv("OTEL_EXPORTER_OTLP_PROTOCOL", tc.protocol) got, err := NewLogExporter(context.Background()) assert.NoError(t, err) t.Cleanup(func() { assert.NoError(t, got.Shutdown(context.Background())) }) assert.Implements(t, new(log.Exporter), got) // Implementation detail hack. This may break when bumping OTLP exporter modules as it uses unexported API. clientType := reflect.Indirect(reflect.ValueOf(got)).FieldByName("client").Type() assert.Equal(t, tc.clientType, clientType.String()) }) } } func TestLogExporterOTLPWithDedicatedProtocol(t *testing.T) { t.Setenv("OTEL_LOGS_EXPORTER", "otlp") for _, tc := range []struct { protocol, clientType string }{ {"http/protobuf", "atomic.Pointer[go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp.client]"}, {"grpc", "otlploggrpc.logClient"}, {"", "atomic.Pointer[go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp.client]"}, } { t.Run(fmt.Sprintf("protocol=%q", tc.protocol), func(t *testing.T) { t.Setenv("OTEL_EXPORTER_OTLP_LOGS_PROTOCOL", tc.protocol) got, err := NewLogExporter(context.Background()) assert.NoError(t, err) t.Cleanup(func() { assert.NoError(t, got.Shutdown(context.Background())) }) assert.Implements(t, new(log.Exporter), got) // Implementation detail hack. This may break when bumping OTLP exporter modules as it uses unexported API. clientType := reflect.Indirect(reflect.ValueOf(got)).FieldByName("client").Type() assert.Equal(t, tc.clientType, clientType.String()) }) } } func TestLogExporterOTLPOverInvalidProtocol(t *testing.T) { t.Setenv("OTEL_LOGS_EXPORTER", "otlp") t.Setenv("OTEL_EXPORTER_OTLP_PROTOCOL", "invalid-protocol") _, err := NewLogExporter(context.Background()) assert.Error(t, err) } open-telemetry-opentelemetry-go-contrib-e5abccb/exporters/autoexport/metrics.go000066400000000000000000000237101470323427300305160ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package autoexport // import "go.opentelemetry.io/contrib/exporters/autoexport" import ( "context" "errors" "fmt" "net" "net/http" "os" "strings" "time" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" prometheusbridge "go.opentelemetry.io/contrib/bridges/prometheus" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" promexporter "go.opentelemetry.io/otel/exporters/prometheus" "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" "go.opentelemetry.io/otel/sdk/metric" ) const otelExporterOTLPMetricsProtoEnvKey = "OTEL_EXPORTER_OTLP_METRICS_PROTOCOL" // MetricOption applies an autoexport configuration option. type MetricOption = option[metric.Reader] // WithFallbackMetricReader sets the fallback exporter to use when no exporter // is configured through the OTEL_METRICS_EXPORTER environment variable. func WithFallbackMetricReader(metricReaderFactory func(ctx context.Context) (metric.Reader, error)) MetricOption { return withFallbackFactory[metric.Reader](metricReaderFactory) } // NewMetricReader returns a configured [go.opentelemetry.io/otel/sdk/metric.Reader] // defined using the environment variables described below. // // OTEL_METRICS_EXPORTER defines the metrics exporter; supported values: // - "none" - "no operation" exporter // - "otlp" (default) - OTLP exporter; see [go.opentelemetry.io/otel/exporters/otlp/otlpmetric] // - "prometheus" - Prometheus exporter + HTTP server; see [go.opentelemetry.io/otel/exporters/prometheus] // - "console" - Standard output exporter; see [go.opentelemetry.io/otel/exporters/stdout/stdoutmetric] // // OTEL_EXPORTER_OTLP_PROTOCOL defines OTLP exporter's transport protocol; // supported values: // - "grpc" - protobuf-encoded data using gRPC wire format over HTTP/2 connection; // see: [go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc] // - "http/protobuf" (default) - protobuf-encoded data over HTTP connection; // see: [go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp] // // OTEL_EXPORTER_OTLP_METRICS_PROTOCOL defines OTLP exporter's transport protocol for the metrics signal; // supported values are the same as OTEL_EXPORTER_OTLP_PROTOCOL. // // OTEL_EXPORTER_PROMETHEUS_HOST (defaulting to "localhost") and // OTEL_EXPORTER_PROMETHEUS_PORT (defaulting to 9464) define the host and port for the // Prometheus exporter's HTTP server. // // Experimental: OTEL_METRICS_PRODUCERS can be used to configure metric producers. // supported values: prometheus, none. Multiple values can be specified separated by commas. // // An error is returned if an environment value is set to an unhandled value. // // Use [RegisterMetricReader] to handle more values of OTEL_METRICS_EXPORTER. // Use [RegisterMetricProducer] to handle more values of OTEL_METRICS_PRODUCERS. // // Use [WithFallbackMetricReader] option to change the returned exporter // when OTEL_METRICS_EXPORTER is unset or empty. // // Use [IsNoneMetricReader] to check if the returned exporter is a "no operation" exporter. func NewMetricReader(ctx context.Context, opts ...MetricOption) (metric.Reader, error) { return metricsSignal.create(ctx, opts...) } // RegisterMetricReader sets the MetricReader factory to be used when the // OTEL_METRICS_EXPORTERS environment variable contains the exporter name. This // will panic if name has already been registered. func RegisterMetricReader(name string, factory func(context.Context) (metric.Reader, error)) { must(metricsSignal.registry.store(name, factory)) } // RegisterMetricProducer sets the MetricReader factory to be used when the // OTEL_METRICS_PRODUCERS environment variable contains the producer name. This // will panic if name has already been registered. func RegisterMetricProducer(name string, factory func(context.Context) (metric.Producer, error)) { must(metricsProducers.registry.store(name, factory)) } // WithFallbackMetricProducer sets the fallback producer to use when no producer // is configured through the OTEL_METRICS_PRODUCERS environment variable. func WithFallbackMetricProducer(producerFactory func(ctx context.Context) (metric.Producer, error)) { metricsProducers.fallbackProducer = producerFactory } var ( metricsSignal = newSignal[metric.Reader]("OTEL_METRICS_EXPORTER") metricsProducers = newProducerRegistry("OTEL_METRICS_PRODUCERS") ) func init() { RegisterMetricReader("otlp", func(ctx context.Context) (metric.Reader, error) { producers, err := metricsProducers.create(ctx) if err != nil { return nil, err } readerOpts := []metric.PeriodicReaderOption{} for _, producer := range producers { readerOpts = append(readerOpts, metric.WithProducer(producer)) } proto := os.Getenv(otelExporterOTLPMetricsProtoEnvKey) if proto == "" { proto = os.Getenv(otelExporterOTLPProtoEnvKey) } // Fallback to default, http/protobuf. if proto == "" { proto = "http/protobuf" } switch proto { case "grpc": r, err := otlpmetricgrpc.New(ctx) if err != nil { return nil, err } return metric.NewPeriodicReader(r, readerOpts...), nil case "http/protobuf": r, err := otlpmetrichttp.New(ctx) if err != nil { return nil, err } return metric.NewPeriodicReader(r, readerOpts...), nil default: return nil, errInvalidOTLPProtocol } }) RegisterMetricReader("console", func(ctx context.Context) (metric.Reader, error) { producers, err := metricsProducers.create(ctx) if err != nil { return nil, err } readerOpts := []metric.PeriodicReaderOption{} for _, producer := range producers { readerOpts = append(readerOpts, metric.WithProducer(producer)) } r, err := stdoutmetric.New() if err != nil { return nil, err } return metric.NewPeriodicReader(r, readerOpts...), nil }) RegisterMetricReader("none", func(ctx context.Context) (metric.Reader, error) { return newNoopMetricReader(), nil }) RegisterMetricReader("prometheus", func(ctx context.Context) (metric.Reader, error) { // create an isolated registry instead of using the global registry -- // the user might not want to mix OTel with non-OTel metrics. // Those that want to comingle metrics from global registry can use // OTEL_METRICS_PRODUCERS=prometheus reg := prometheus.NewRegistry() exporterOpts := []promexporter.Option{promexporter.WithRegisterer(reg)} producers, err := metricsProducers.create(ctx) if err != nil { return nil, err } for _, producer := range producers { exporterOpts = append(exporterOpts, promexporter.WithProducer(producer)) } reader, err := promexporter.New(exporterOpts...) if err != nil { return nil, err } mux := http.NewServeMux() mux.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{Registry: reg})) server := http.Server{ // Timeouts are necessary to make a server resilient to attacks, but ListenAndServe doesn't set any. // We use values from this example: https://blog.cloudflare.com/exposing-go-on-the-internet/#:~:text=There%20are%20three%20main%20timeouts ReadTimeout: 5 * time.Second, WriteTimeout: 10 * time.Second, IdleTimeout: 120 * time.Second, Handler: mux, } // environment variable names and defaults specified at https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/#prometheus-exporter host := getenv("OTEL_EXPORTER_PROMETHEUS_HOST", "localhost") port := getenv("OTEL_EXPORTER_PROMETHEUS_PORT", "9464") addr := host + ":" + port lis, err := net.Listen("tcp", addr) if err != nil { return nil, errors.Join( fmt.Errorf("binding address %s for Prometheus exporter: %w", addr, err), reader.Shutdown(ctx), ) } go func() { if err := server.Serve(lis); err != nil && !errors.Is(err, http.ErrServerClosed) { otel.Handle(fmt.Errorf("the Prometheus HTTP server exited unexpectedly: %w", err)) } }() return readerWithServer{lis.Addr(), reader, &server}, nil }) RegisterMetricProducer("prometheus", func(ctx context.Context) (metric.Producer, error) { return prometheusbridge.NewMetricProducer(), nil }) RegisterMetricProducer("none", func(ctx context.Context) (metric.Producer, error) { return newNoopMetricProducer(), nil }) } type readerWithServer struct { addr net.Addr metric.Reader server *http.Server } func (rws readerWithServer) Shutdown(ctx context.Context) error { return errors.Join( rws.Reader.Shutdown(ctx), rws.server.Shutdown(ctx), ) } func getenv(key, fallback string) string { result, ok := os.LookupEnv(key) if !ok { return fallback } return result } type producerRegistry struct { envKey string fallbackProducer func(context.Context) (metric.Producer, error) registry *registry[metric.Producer] } func newProducerRegistry(envKey string) producerRegistry { return producerRegistry{ envKey: envKey, registry: ®istry[metric.Producer]{ names: make(map[string]func(context.Context) (metric.Producer, error)), }, } } func (pr producerRegistry) create(ctx context.Context) ([]metric.Producer, error) { expType := os.Getenv(pr.envKey) if expType == "" { if pr.fallbackProducer != nil { producer, err := pr.fallbackProducer(ctx) if err != nil { return nil, err } return []metric.Producer{producer}, nil } return nil, nil } producers := dedupedMetricProducers(expType) metricProducers := make([]metric.Producer, 0, len(producers)) for _, producer := range producers { producer, err := pr.registry.load(ctx, producer) if err != nil { return nil, err } metricProducers = append(metricProducers, producer) } return metricProducers, nil } func dedupedMetricProducers(envValue string) []string { producers := make(map[string]struct{}) for _, producer := range strings.Split(envValue, ",") { producers[producer] = struct{}{} } result := make([]string, 0, len(producers)) for producer := range producers { result = append(result, producer) } return result } open-telemetry-opentelemetry-go-contrib-e5abccb/exporters/autoexport/metrics_test.go000066400000000000000000000247421470323427300315630ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package autoexport // import "go.opentelemetry.io/contrib/exporters/autoexport" import ( "context" "fmt" "io" "net/http" "net/http/httptest" "reflect" "runtime/debug" "strings" "testing" "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/goleak" "google.golang.org/protobuf/proto" prometheusbridge "go.opentelemetry.io/contrib/bridges/prometheus" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/sdk/metric" otlpmetrics "go.opentelemetry.io/proto/otlp/collector/metrics/v1" ) func TestMetricExporterNone(t *testing.T) { t.Setenv("OTEL_METRICS_EXPORTER", "none") got, err := NewMetricReader(context.Background()) assert.NoError(t, err) t.Cleanup(func() { assert.NoError(t, got.Shutdown(context.Background())) }) assert.True(t, IsNoneMetricReader(got)) } func TestMetricExporterConsole(t *testing.T) { t.Setenv("OTEL_METRICS_EXPORTER", "console") got, err := NewMetricReader(context.Background()) assert.NoError(t, err) t.Cleanup(func() { assert.NoError(t, got.Shutdown(context.Background())) }) assert.IsType(t, &metric.PeriodicReader{}, got) exporterType := reflect.Indirect(reflect.ValueOf(got)).FieldByName("exporter").Elem().Type() assert.Equal(t, "*stdoutmetric.exporter", exporterType.String()) } func TestMetricExporterOTLP(t *testing.T) { t.Setenv("OTEL_METRICS_EXPORTER", "otlp") for _, tc := range []struct { protocol, exporterType string }{ {"http/protobuf", "*otlpmetrichttp.Exporter"}, {"", "*otlpmetrichttp.Exporter"}, {"grpc", "*otlpmetricgrpc.Exporter"}, } { t.Run(fmt.Sprintf("protocol=%q", tc.protocol), func(t *testing.T) { t.Setenv("OTEL_EXPORTER_OTLP_PROTOCOL", tc.protocol) got, err := NewMetricReader(context.Background()) assert.NoError(t, err) t.Cleanup(func() { assert.NoError(t, got.Shutdown(context.Background())) }) assert.IsType(t, &metric.PeriodicReader{}, got) // Implementation detail hack. This may break when bumping OTLP exporter modules as it uses unexported API. exporterType := reflect.Indirect(reflect.ValueOf(got)).FieldByName("exporter").Elem().Type() assert.Equal(t, tc.exporterType, exporterType.String()) }) } } func TestMetricExporterOTLPWithDedicatedProtocol(t *testing.T) { t.Setenv("OTEL_METRICS_EXPORTER", "otlp") for _, tc := range []struct { protocol, exporterType string }{ {"http/protobuf", "*otlpmetrichttp.Exporter"}, {"", "*otlpmetrichttp.Exporter"}, {"grpc", "*otlpmetricgrpc.Exporter"}, } { t.Run(fmt.Sprintf("protocol=%q", tc.protocol), func(t *testing.T) { t.Setenv("OTEL_EXPORTER_OTLP_METRICS_PROTOCOL", tc.protocol) got, err := NewMetricReader(context.Background()) assert.NoError(t, err) t.Cleanup(func() { assert.NoError(t, got.Shutdown(context.Background())) }) assert.IsType(t, &metric.PeriodicReader{}, got) // Implementation detail hack. This may break when bumping OTLP exporter modules as it uses unexported API. exporterType := reflect.Indirect(reflect.ValueOf(got)).FieldByName("exporter").Elem().Type() assert.Equal(t, tc.exporterType, exporterType.String()) }) } } func TestMetricExporterOTLPOverInvalidProtocol(t *testing.T) { t.Setenv("OTEL_METRICS_EXPORTER", "otlp") t.Setenv("OTEL_EXPORTER_OTLP_PROTOCOL", "invalid-protocol") _, err := NewMetricReader(context.Background()) assert.Error(t, err) } func assertNoOtelHandleErrors(t *testing.T) { h := otel.GetErrorHandler() t.Cleanup(func() { otel.SetErrorHandler(h) }) otel.SetErrorHandler(otel.ErrorHandlerFunc(func(cause error) { t.Errorf("expected to calls to otel.Handle but got %v from %s", cause, debug.Stack()) })) } func TestMetricExporterPrometheus(t *testing.T) { assertNoOtelHandleErrors(t) t.Setenv("OTEL_METRICS_EXPORTER", "prometheus") t.Setenv("OTEL_EXPORTER_PROMETHEUS_PORT", "0") r, err := NewMetricReader(context.Background()) assert.NoError(t, err) // pull-based exporters like Prometheus need to be registered mp := metric.NewMeterProvider(metric.WithReader(r)) rws, ok := r.(readerWithServer) if !ok { t.Errorf("expected readerWithServer but got %v", r) } resp, err := http.Get(fmt.Sprintf("http://%s/metrics", rws.addr)) require.NoError(t, err) defer func() { assert.NoError(t, resp.Body.Close()) }() body, err := io.ReadAll(resp.Body) require.NoError(t, err) assert.Contains(t, string(body), "# HELP") assert.NoError(t, mp.Shutdown(context.Background())) goleak.VerifyNone(t) } func TestMetricExporterPrometheusInvalidPort(t *testing.T) { t.Setenv("OTEL_METRICS_EXPORTER", "prometheus") t.Setenv("OTEL_EXPORTER_PROMETHEUS_PORT", "invalid-port") _, err := NewMetricReader(context.Background()) assert.ErrorContains(t, err, "binding") } func TestMetricProducerPrometheusWithOTLPExporter(t *testing.T) { assertNoOtelHandleErrors(t) requestWaitChan := make(chan struct{}) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { body, err := io.ReadAll(r.Body) assert.NoError(t, err) assert.NoError(t, r.Body.Close()) // Now parse the otlp proto message from request body. req := &otlpmetrics.ExportMetricsServiceRequest{} assert.NoError(t, proto.Unmarshal(body, req)) // This is 0 without the producer registered. assert.NotZero(t, req.ResourceMetrics) assert.NotZero(t, req.ResourceMetrics[0].ScopeMetrics) assert.NotZero(t, req.ResourceMetrics[0].ScopeMetrics[0].Metrics) close(requestWaitChan) })) t.Setenv("OTEL_METRICS_EXPORTER", "otlp") t.Setenv("OTEL_EXPORTER_OTLP_ENDPOINT", ts.URL) t.Setenv("OTEL_EXPORTER_OTLP_PROTOCOL", "http/protobuf") t.Setenv("OTEL_METRICS_PRODUCERS", "prometheus") r, err := NewMetricReader(context.Background()) assert.NoError(t, err) assert.IsType(t, &metric.PeriodicReader{}, r) // Register it with a meter provider to ensure it is used. // mp.Shutdown errors out because r.Shutdown closes the reader. metric.NewMeterProvider(metric.WithReader(r)) // Shutdown actually makes an export call. assert.NoError(t, r.Shutdown(context.Background())) <-requestWaitChan ts.Close() goleak.VerifyNone(t) } func TestMetricProducerPrometheusWithPrometheusExporter(t *testing.T) { assertNoOtelHandleErrors(t) t.Setenv("OTEL_METRICS_EXPORTER", "prometheus") t.Setenv("OTEL_EXPORTER_PROMETHEUS_PORT", "0") t.Setenv("OTEL_METRICS_PRODUCERS", "prometheus") r, err := NewMetricReader(context.Background()) assert.NoError(t, err) // pull-based exporters like Prometheus need to be registered mp := metric.NewMeterProvider(metric.WithReader(r)) rws, ok := r.(readerWithServer) if !ok { t.Errorf("expected readerWithServer but got %v", r) } resp, err := http.Get(fmt.Sprintf("http://%s/metrics", rws.addr)) require.NoError(t, err) defer func() { assert.NoError(t, resp.Body.Close()) }() body, err := io.ReadAll(resp.Body) require.NoError(t, err) // By default there are two metrics exporter. target_info and promhttp_metric_handler_errors_total. // But by including the prometheus producer we should have more. assert.Greater(t, strings.Count(string(body), "# HELP"), 2) assert.NoError(t, mp.Shutdown(context.Background())) goleak.VerifyNone(t) } func TestMetricProducerFallbackWithPrometheusExporter(t *testing.T) { assertNoOtelHandleErrors(t) reg := prometheus.NewRegistry() someDummyMetric := prometheus.NewCounter(prometheus.CounterOpts{ Name: "dummy_metric", Help: "dummy metric", }) reg.MustRegister(someDummyMetric) WithFallbackMetricProducer(func(context.Context) (metric.Producer, error) { return prometheusbridge.NewMetricProducer(prometheusbridge.WithGatherer(reg)), nil }) t.Setenv("OTEL_METRICS_EXPORTER", "prometheus") t.Setenv("OTEL_EXPORTER_PROMETHEUS_PORT", "0") r, err := NewMetricReader(context.Background()) assert.NoError(t, err) // pull-based exporters like Prometheus need to be registered mp := metric.NewMeterProvider(metric.WithReader(r)) rws, ok := r.(readerWithServer) if !ok { t.Errorf("expected readerWithServer but got %v", r) } resp, err := http.Get(fmt.Sprintf("http://%s/metrics", rws.addr)) require.NoError(t, err) defer func() { assert.NoError(t, resp.Body.Close()) }() body, err := io.ReadAll(resp.Body) require.NoError(t, err) assert.Contains(t, string(body), "HELP dummy_metric_total dummy metric") assert.NoError(t, mp.Shutdown(context.Background())) goleak.VerifyNone(t) } func TestMultipleMetricProducerWithOTLPExporter(t *testing.T) { requestWaitChan := make(chan struct{}) reg1 := prometheus.NewRegistry() someDummyMetric := prometheus.NewCounter(prometheus.CounterOpts{ Name: "dummy_metric_1", Help: "dummy metric ONE", }) reg1.MustRegister(someDummyMetric) reg2 := prometheus.NewRegistry() someOtherDummyMetric := prometheus.NewCounter(prometheus.CounterOpts{ Name: "dummy_metric_2", Help: "dummy metric TWO", }) reg2.MustRegister(someOtherDummyMetric) RegisterMetricProducer("first_producer", func(context.Context) (metric.Producer, error) { return prometheusbridge.NewMetricProducer(prometheusbridge.WithGatherer(reg1)), nil }) RegisterMetricProducer("second_producer", func(context.Context) (metric.Producer, error) { return prometheusbridge.NewMetricProducer(prometheusbridge.WithGatherer(reg2)), nil }) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { body, err := io.ReadAll(r.Body) assert.NoError(t, err) assert.NoError(t, r.Body.Close()) // Now parse the otlp proto message from request body. req := &otlpmetrics.ExportMetricsServiceRequest{} assert.NoError(t, proto.Unmarshal(body, req)) metricNames := []string{} sm := req.ResourceMetrics[0].ScopeMetrics for i := 0; i < len(sm); i++ { m := sm[i].Metrics for i := 0; i < len(m); i++ { metricNames = append(metricNames, m[i].Name) } } assert.ElementsMatch(t, metricNames, []string{"dummy_metric_1", "dummy_metric_2"}) close(requestWaitChan) })) t.Setenv("OTEL_METRICS_EXPORTER", "otlp") t.Setenv("OTEL_EXPORTER_OTLP_ENDPOINT", ts.URL) t.Setenv("OTEL_EXPORTER_OTLP_PROTOCOL", "http/protobuf") t.Setenv("OTEL_METRICS_PRODUCERS", "first_producer,second_producer,first_producer") r, err := NewMetricReader(context.Background()) assert.NoError(t, err) assert.IsType(t, &metric.PeriodicReader{}, r) // Register it with a meter provider to ensure it is used. // mp.Shutdown errors out because r.Shutdown closes the reader. metric.NewMeterProvider(metric.WithReader(r)) // Shutdown actually makes an export call. assert.NoError(t, r.Shutdown(context.Background())) <-requestWaitChan ts.Close() goleak.VerifyNone(t) } open-telemetry-opentelemetry-go-contrib-e5abccb/exporters/autoexport/noop.go000066400000000000000000000046741470323427300300330ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package autoexport // import "go.opentelemetry.io/contrib/exporters/autoexport" import ( "context" "go.opentelemetry.io/otel/sdk/log" "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/trace" ) // noopSpanExporter is an implementation of trace.SpanExporter that performs no operations. type noopSpanExporter struct{} var _ trace.SpanExporter = noopSpanExporter{} // ExportSpans is part of trace.SpanExporter interface. func (e noopSpanExporter) ExportSpans(ctx context.Context, spans []trace.ReadOnlySpan) error { return nil } // Shutdown is part of trace.SpanExporter interface. func (e noopSpanExporter) Shutdown(ctx context.Context) error { return nil } // IsNoneSpanExporter returns true for the exporter returned by [NewSpanExporter] // when OTEL_TRACES_EXPORTER environment variable is set to "none". func IsNoneSpanExporter(e trace.SpanExporter) bool { _, ok := e.(noopSpanExporter) return ok } type noopMetricReader struct { *metric.ManualReader } func newNoopMetricReader() noopMetricReader { return noopMetricReader{metric.NewManualReader()} } // IsNoneMetricReader returns true for the exporter returned by [NewMetricReader] // when OTEL_METRICS_EXPORTER environment variable is set to "none". func IsNoneMetricReader(e metric.Reader) bool { _, ok := e.(noopMetricReader) return ok } type noopMetricProducer struct{} func (e noopMetricProducer) Produce(ctx context.Context) ([]metricdata.ScopeMetrics, error) { return nil, nil } func newNoopMetricProducer() noopMetricProducer { return noopMetricProducer{} } // noopLogExporter is an implementation of log.SpanExporter that performs no operations. type noopLogExporter struct{} var _ log.Exporter = noopLogExporter{} // ExportSpans is part of log.Exporter interface. func (e noopLogExporter) Export(ctx context.Context, records []log.Record) error { return nil } // Shutdown is part of log.Exporter interface. func (e noopLogExporter) Shutdown(ctx context.Context) error { return nil } // ForceFlush is part of log.Exporter interface. func (e noopLogExporter) ForceFlush(ctx context.Context) error { return nil } // IsNoneLogExporter returns true for the exporter returned by [NewLogExporter] // when OTEL_LOGSS_EXPORTER environment variable is set to "none". func IsNoneLogExporter(e log.Exporter) bool { _, ok := e.(noopLogExporter) return ok } open-telemetry-opentelemetry-go-contrib-e5abccb/exporters/autoexport/registry.go000066400000000000000000000042021470323427300307130ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package autoexport // import "go.opentelemetry.io/contrib/exporters/autoexport" import ( "context" "errors" "fmt" "sync" ) const otelExporterOTLPProtoEnvKey = "OTEL_EXPORTER_OTLP_PROTOCOL" // registry maintains a map of exporter names to exporter factories // func(context.Context) (T, error) that is safe for concurrent use by multiple // goroutines without additional locking or coordination. type registry[T any] struct { mu sync.Mutex names map[string]func(context.Context) (T, error) } var ( // errUnknownExporterProducer is returned when an unknown exporter name is used in // the OTEL_*_EXPORTER or OTEL_METRICS_PRODUCERS environment variables. errUnknownExporterProducer = errors.New("unknown exporter or metrics producer") // errInvalidOTLPProtocol is returned when an invalid protocol is used in // the OTEL_EXPORTER_OTLP_PROTOCOL environment variable. errInvalidOTLPProtocol = errors.New("invalid OTLP protocol - should be one of ['grpc', 'http/protobuf']") // errDuplicateRegistration is returned when an duplicate registration is detected. errDuplicateRegistration = errors.New("duplicate registration") ) // load returns tries to find the exporter factory with the key and // then execute the factory, returning the created SpanExporter. // errUnknownExporterProducer is returned if the registration is missing and the error from // executing the factory if not nil. func (r *registry[T]) load(ctx context.Context, key string) (T, error) { r.mu.Lock() defer r.mu.Unlock() factory, ok := r.names[key] if !ok { var zero T return zero, errUnknownExporterProducer } return factory(ctx) } // store sets the factory for a key if is not already in the registry. errDuplicateRegistration // is returned if the registry already contains key. func (r *registry[T]) store(key string, factory func(context.Context) (T, error)) error { r.mu.Lock() defer r.mu.Unlock() if _, ok := r.names[key]; ok { return fmt.Errorf("%w: %q", errDuplicateRegistration, key) } r.names[key] = factory return nil } func must(err error) { if err != nil { panic(err) } } open-telemetry-opentelemetry-go-contrib-e5abccb/exporters/autoexport/registry_test.go000066400000000000000000000044111470323427300317540ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package autoexport import ( "context" "errors" "fmt" "sync" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) type testType struct{ string } func factory(val string) func(ctx context.Context) (*testType, error) { return func(ctx context.Context) (*testType, error) { return &testType{val}, nil } } func newTestRegistry() registry[*testType] { return registry[*testType]{ names: make(map[string]func(context.Context) (*testType, error)), } } func TestCanStoreExporterFactory(t *testing.T) { r := newTestRegistry() require.NoError(t, r.store("first", factory("first"))) } func TestLoadOfUnknownExporterReturnsError(t *testing.T) { r := newTestRegistry() exp, err := r.load(context.Background(), "non-existent") assert.Equal(t, err, errUnknownExporterProducer, "empty registry should hold nothing") assert.Nil(t, exp, "non-nil exporter returned") } func TestRegistryIsConcurrentSafe(t *testing.T) { const exporterName = "stdout" r := newTestRegistry() require.NoError(t, r.store(exporterName, factory("stdout"))) var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() assert.ErrorIs(t, r.store(exporterName, factory("stdout")), errDuplicateRegistration) }() wg.Add(1) go func() { defer wg.Done() _, err := r.load(context.Background(), exporterName) assert.NoError(t, err, "missing exporter in registry") }() wg.Wait() } func TestSubsequentCallsToGetExporterReturnsNewInstances(t *testing.T) { r := newTestRegistry() const key = "key" assert.NoError(t, r.store(key, factory(key))) exp1, err := r.load(context.Background(), key) assert.NoError(t, err) exp2, err := r.load(context.Background(), key) assert.NoError(t, err) assert.NotSame(t, exp1, exp2) } func TestRegistryErrorsOnDuplicateRegisterCalls(t *testing.T) { r := newTestRegistry() const exporterName = "custom" assert.NoError(t, r.store(exporterName, factory(exporterName))) errString := fmt.Sprintf("%s: %q", errDuplicateRegistration, exporterName) assert.ErrorContains(t, r.store(exporterName, factory(exporterName)), errString) } func TestMust(t *testing.T) { assert.Panics(t, func() { must(errors.New("test")) }) assert.NotPanics(t, func() { must(nil) }) } open-telemetry-opentelemetry-go-contrib-e5abccb/exporters/autoexport/signal.go000066400000000000000000000024061470323427300303240ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package autoexport // import "go.opentelemetry.io/contrib/exporters/autoexport" import ( "context" "os" ) type signal[T any] struct { envKey string registry *registry[T] } func newSignal[T any](envKey string) signal[T] { return signal[T]{ envKey: envKey, registry: ®istry[T]{ names: make(map[string]func(context.Context) (T, error)), }, } } func (s signal[T]) create(ctx context.Context, opts ...option[T]) (T, error) { var cfg config[T] for _, opt := range opts { opt.apply(&cfg) } expType := os.Getenv(s.envKey) if expType == "" { if cfg.fallbackFactory != nil { return cfg.fallbackFactory(ctx) } expType = "otlp" } return s.registry.load(ctx, expType) } type config[T any] struct { fallbackFactory func(ctx context.Context) (T, error) } type option[T any] interface { apply(cfg *config[T]) } type optionFunc[T any] func(cfg *config[T]) //lint:ignore U1000 https://github.com/dominikh/go-tools/issues/1440 func (fn optionFunc[T]) apply(cfg *config[T]) { fn(cfg) } func withFallbackFactory[T any](fallbackFactory func(ctx context.Context) (T, error)) option[T] { return optionFunc[T](func(cfg *config[T]) { cfg.fallbackFactory = fallbackFactory }) } open-telemetry-opentelemetry-go-contrib-e5abccb/exporters/autoexport/signal_test.go000066400000000000000000000034011470323427300313570ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package autoexport // import "go.opentelemetry.io/contrib/exporters/autoexport" import ( "context" "errors" "testing" "github.com/stretchr/testify/assert" ) func TestOTLPExporterReturnedWhenNoEnvOrFallbackExporterConfigured(t *testing.T) { ts := newSignal[*testType]("TEST_TYPE_KEY") assert.NoError(t, ts.registry.store("otlp", factory("test-otlp-exporter"))) exp, err := ts.create(context.Background()) assert.NoError(t, err) assert.Equal(t, exp.string, "test-otlp-exporter") } func TestFallbackExporterReturnedWhenNoEnvExporterConfigured(t *testing.T) { ts := newSignal[*testType]("TEST_TYPE_KEY") exp, err := ts.create(context.Background(), withFallbackFactory(factory("test-fallback-exporter"))) assert.NoError(t, err) assert.Equal(t, exp.string, "test-fallback-exporter") } func TestFallbackExporterFactoryErrorReturnedWhenNoEnvExporterConfiguredAndFallbackFactoryReturnsAnError(t *testing.T) { ts := newSignal[*testType]("TEST_TYPE_KEY") expectedErr := errors.New("error expected to return") errFactory := func(ctx context.Context) (*testType, error) { return nil, expectedErr } exp, err := ts.create(context.Background(), withFallbackFactory(errFactory)) assert.ErrorIs(t, err, expectedErr) assert.Nil(t, exp) } func TestEnvExporterIsPreferredOverFallbackExporter(t *testing.T) { envVariable := "TEST_TYPE_KEY" ts := newSignal[*testType](envVariable) expName := "test-env-exporter-name" t.Setenv(envVariable, expName) assert.NoError(t, ts.registry.store(expName, factory("test-env-exporter"))) exp, err := ts.create(context.Background(), withFallbackFactory(factory("test-fallback-exporter"))) assert.NoError(t, err) assert.Equal(t, exp.string, "test-env-exporter") } open-telemetry-opentelemetry-go-contrib-e5abccb/exporters/autoexport/spans.go000066400000000000000000000073411470323427300301760ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package autoexport // import "go.opentelemetry.io/contrib/exporters/autoexport" import ( "context" "os" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/sdk/trace" ) const otelExporterOTLPTracesProtoEnvKey = "OTEL_EXPORTER_OTLP_TRACES_PROTOCOL" // SpanOption applies an autoexport configuration option. type SpanOption = option[trace.SpanExporter] // Option applies an autoexport configuration option. // // Deprecated: Use SpanOption. type Option = SpanOption // WithFallbackSpanExporter sets the fallback exporter to use when no exporter // is configured through the OTEL_TRACES_EXPORTER environment variable. func WithFallbackSpanExporter(spanExporterFactory func(ctx context.Context) (trace.SpanExporter, error)) SpanOption { return withFallbackFactory[trace.SpanExporter](spanExporterFactory) } // NewSpanExporter returns a configured [go.opentelemetry.io/otel/sdk/trace.SpanExporter] // defined using the environment variables described below. // // OTEL_TRACES_EXPORTER defines the traces exporter; supported values: // - "none" - "no operation" exporter // - "otlp" (default) - OTLP exporter; see [go.opentelemetry.io/otel/exporters/otlp/otlptrace] // - "console" - Standard output exporter; see [go.opentelemetry.io/otel/exporters/stdout/stdouttrace] // // OTEL_EXPORTER_OTLP_PROTOCOL defines OTLP exporter's transport protocol; // supported values: // - "grpc" - protobuf-encoded data using gRPC wire format over HTTP/2 connection; // see: [go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc] // - "http/protobuf" (default) - protobuf-encoded data over HTTP connection; // see: [go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp] // // OTEL_EXPORTER_OTLP_TRACES_PROTOCOL defines OTLP exporter's transport protocol for the traces signal; // supported values are the same as OTEL_EXPORTER_OTLP_PROTOCOL. // // An error is returned if an environment value is set to an unhandled value. // // Use [RegisterSpanExporter] to handle more values of OTEL_TRACES_EXPORTER. // // Use [WithFallbackSpanExporter] option to change the returned exporter // when OTEL_TRACES_EXPORTER is unset or empty. // // Use [IsNoneSpanExporter] to check if the returned exporter is a "no operation" exporter. func NewSpanExporter(ctx context.Context, opts ...SpanOption) (trace.SpanExporter, error) { return tracesSignal.create(ctx, opts...) } // RegisterSpanExporter sets the SpanExporter factory to be used when the // OTEL_TRACES_EXPORTER environment variable contains the exporter name. This // will panic if name has already been registered. func RegisterSpanExporter(name string, factory func(context.Context) (trace.SpanExporter, error)) { must(tracesSignal.registry.store(name, factory)) } var tracesSignal = newSignal[trace.SpanExporter]("OTEL_TRACES_EXPORTER") func init() { RegisterSpanExporter("otlp", func(ctx context.Context) (trace.SpanExporter, error) { proto := os.Getenv(otelExporterOTLPTracesProtoEnvKey) if proto == "" { proto = os.Getenv(otelExporterOTLPProtoEnvKey) } // Fallback to default, http/protobuf. if proto == "" { proto = "http/protobuf" } switch proto { case "grpc": return otlptracegrpc.New(ctx) case "http/protobuf": return otlptracehttp.New(ctx) default: return nil, errInvalidOTLPProtocol } }) RegisterSpanExporter("console", func(ctx context.Context) (trace.SpanExporter, error) { return stdouttrace.New() }) RegisterSpanExporter("none", func(ctx context.Context) (trace.SpanExporter, error) { return noopSpanExporter{}, nil }) } open-telemetry-opentelemetry-go-contrib-e5abccb/exporters/autoexport/spans_test.go000066400000000000000000000055111470323427300312320ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package autoexport // import "go.opentelemetry.io/contrib/exporters/autoexport" import ( "context" "fmt" "reflect" "testing" "go.opentelemetry.io/otel/exporters/otlp/otlptrace" "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "github.com/stretchr/testify/assert" ) func TestSpanExporterNone(t *testing.T) { t.Setenv("OTEL_TRACES_EXPORTER", "none") got, err := NewSpanExporter(context.Background()) assert.NoError(t, err) t.Cleanup(func() { assert.NoError(t, got.Shutdown(context.Background())) }) assert.True(t, IsNoneSpanExporter(got)) } func TestSpanExporterConsole(t *testing.T) { t.Setenv("OTEL_TRACES_EXPORTER", "console") got, err := NewSpanExporter(context.Background()) assert.NoError(t, err) assert.IsType(t, &stdouttrace.Exporter{}, got) } func TestSpanExporterOTLP(t *testing.T) { t.Setenv("OTEL_TRACES_EXPORTER", "otlp") for _, tc := range []struct { protocol, clientType string }{ {"http/protobuf", "*otlptracehttp.client"}, {"", "*otlptracehttp.client"}, {"grpc", "*otlptracegrpc.client"}, } { t.Run(fmt.Sprintf("protocol=%q", tc.protocol), func(t *testing.T) { t.Setenv("OTEL_EXPORTER_OTLP_PROTOCOL", tc.protocol) got, err := NewSpanExporter(context.Background()) assert.NoError(t, err) t.Cleanup(func() { assert.NoError(t, got.Shutdown(context.Background())) }) assert.IsType(t, &otlptrace.Exporter{}, got) // Implementation detail hack. This may break when bumping OTLP exporter modules as it uses unexported API. clientType := reflect.Indirect(reflect.ValueOf(got)).FieldByName("client").Elem().Type() assert.Equal(t, tc.clientType, clientType.String()) }) } } func TestSpanExporterOTLPWithDedicatedProtocol(t *testing.T) { t.Setenv("OTEL_TRACES_EXPORTER", "otlp") for _, tc := range []struct { protocol, clientType string }{ {"http/protobuf", "*otlptracehttp.client"}, {"", "*otlptracehttp.client"}, {"grpc", "*otlptracegrpc.client"}, } { t.Run(fmt.Sprintf("protocol=%q", tc.protocol), func(t *testing.T) { t.Setenv("OTEL_EXPORTER_OTLP_TRACES_PROTOCOL", tc.protocol) got, err := NewSpanExporter(context.Background()) assert.NoError(t, err) t.Cleanup(func() { assert.NoError(t, got.Shutdown(context.Background())) }) assert.IsType(t, &otlptrace.Exporter{}, got) // Implementation detail hack. This may break when bumping OTLP exporter modules as it uses unexported API. clientType := reflect.Indirect(reflect.ValueOf(got)).FieldByName("client").Elem().Type() assert.Equal(t, tc.clientType, clientType.String()) }) } } func TestSpanExporterOTLPOverInvalidProtocol(t *testing.T) { t.Setenv("OTEL_TRACES_EXPORTER", "otlp") t.Setenv("OTEL_EXPORTER_OTLP_PROTOCOL", "invalid-protocol") _, err := NewSpanExporter(context.Background()) assert.Error(t, err) } open-telemetry-opentelemetry-go-contrib-e5abccb/get_main_pkgs.sh000077500000000000000000000013421470323427300254170ustar00rootroot00000000000000#!/usr/bin/env bash # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 set -euo pipefail top_dir='.' if [[ $# -gt 0 ]]; then top_dir="${1}" fi p=$(pwd) mod_dirs=() # Note `mapfile` does not exist in older bash versions: # https://stackoverflow.com/questions/41475261/need-alternative-to-readarray-mapfile-for-script-on-older-version-of-bash while IFS= read -r line; do mod_dirs+=("$line") done < <(find "${top_dir}" -type f -name 'go.mod' -exec dirname {} \; | sort) for mod_dir in "${mod_dirs[@]}"; do cd "${mod_dir}" while IFS= read -r line; do echo ".${line#${p}}" done < <(go list --find -f '{{.Name}}|{{.Dir}}' ./... | grep '^main|' | cut -f 2- -d '|') cd "${p}" done open-telemetry-opentelemetry-go-contrib-e5abccb/go.mod000066400000000000000000000000541470323427300233560ustar00rootroot00000000000000module go.opentelemetry.io/contrib go 1.22 open-telemetry-opentelemetry-go-contrib-e5abccb/go.sum000066400000000000000000000000001470323427300233720ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/000077500000000000000000000000001470323427300241025ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/README.md000066400000000000000000000023151470323427300253620ustar00rootroot00000000000000# OpenTelemetry Go Source Automatic Instrumentation This package provides a code generation utility that instruments existing source code with [OpenTelemetry]. If you are looking for more details about internal working, see [How it works](./docs/how-it-works.md). ## Project Status :construction: This package is currently work in progress. ## How to use it In order to instrument your project you have to add following call in your entry point function, usually main (you can look at testdata directory for reference) and invoke instrgen tool. ``` func main() { rtlib.AutotelEntryPoint() ``` Instrgen requires three parameters: command, path to project and package(s) pattern we would like to instrument. ``` ./instrgen --inject [path to your go project] [package(s) pattern] ``` Below concrete example with one of test instrumentation that is part of the project. ``` ./instrgen --inject ./testdata/basic ./... ``` ```./...``` works like wildcard in this case and it will instrument all packages in this path, but it can be invoked with specific package as well. ### Compatibility The `instrgen` utility is based on the Go standard library and is platform agnostic. [OpenTelemetry]: https://opentelemetry.io/ open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/doc.go000066400000000000000000000004051470323427300251750ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 /* Package instrgen provides a code generation utility that instruments existing source code with OpenTelemetry. */ package instrgen // import "go.opentelemetry.io/contrib/instrgen" open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/docs/000077500000000000000000000000001470323427300250325ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/docs/flow.png000066400000000000000000002376551470323427300265310ustar00rootroot00000000000000‰PNG  IHDR–j¯û— ªiCCPICC ProfileH‰•—PSéÇ¿{Ó-!Ò ½ Ò %„@A:ØI€PB;²¸‚kADl誀‚«Rd­ˆba,€uAe],ØPy‚»oÞ{óÎÌ™óËÉùþßùîÜoæ\( \±8V E”! öñ`DFE3pƒ8@6€Ä奋YAA±™øw{ß Éx×|Rëßÿÿ¯¦È¤ó€‚Žå§óR>ƒø žX’ê’×[‘!žä„i¤A„{&9~šG&9vŠÑ`ª&4˜0 <™Ë•Ä@f yF&/Ñ!»#l)â E‹vMIIå#|ac¤É‘'õ™±ßéÄÿM3V¦ÉåÆËxú,S†÷¦‹“¹Yÿçãøß–’,ÙÃqr‚Ä7‰JÈ3ëIJõ—±(vaà ùSõSœ õ ›a^:;z†ù\OÙÚä…3'ôæÈt28¡3,H÷ ™aIj°l¯8 ›5Ã\Éì¾Ò¤0Y>AÀ‘ég'„FÌp¦0|á §'…øÏÖ°ey‰4XÖ¿@äã1»¯·ìì)éßWÈ‘­ÍHõ•;Û¿@ÄšÕL”õÆxzÍÖ„ÉêŲ½ÄÉA²zA²,Ÿž"[›¼³kƒdÏ0‘ë4À RA2âÀÈ/O2+3&ÂNgI„ñ  rÃ Žˆg1—amimÀä}~ÞÒ§î!D¿9›Û¨€KÖÄÄĹٜ§Ï@|8›3@î&×wó¤’ÌéÜÔ]Â"4  ´€0æÀØgༀ¡ ,<RÎW€Õ`È`;ØJÁ~p'À)ÐÎËà¸:À}ðô‚ðŒ‚÷`‚ D¨*¤ @f5Ä„\!/( †¢ (ARh5´*€ ¡Rè T ý….C7 NèÔ Co Ï0 &Ã4X6„çÁL˜ûáðR8Nƒ³á\x+\WÀÇázø2| ¾÷Â/á1@‘Pt”ÊÅD±Q¨hTJ‚Z‹ÊG£*P5¨&T+ê.ª5‚ú„Æ¢©hÚíŒöE‡¡yè4ôZôt)úºÝ‚¾‹îC¢¿a( ŒÆ ÃÁDbâ1+0y˜bÌLæ*æ>fó‹ÅÒ±FX¬/6 ›ˆ]…݂݋­Å^Âvbû±c8Ng†sÁ⸸ \nî8î"în÷OÂkã­ñÞøh¼Ÿƒ/ÆWá/àïàñã‚Á‰Hà²Û‡ M„Û„Â8Q‘hDt!†‰ˆ%ÄâUâcâ[‰¤Kr$-" IëI%¤“¤ë¤>Ò'²Ù”Ì&/!KÉ[ÉGÉ—ÈÈo)Š!ÅMÉ l¥TR®PžR>ÊQå,ä8r|¹urerõrwä^Éä äYòËä³å‹åOËß–Q (*°¸ kÊÎ*t+Œ)R­S·(V)ÞPRÂ)*y)ñ•r•)]Qê§¢¨zT6•GÝH=L½J aiF4-‘V@;Ak§*+)Û*‡+¯T.S>¯ÜKGÑ éz2}ý½‹þyŽæÖÁœÍsjæÜ™óAE]Å]E ’¯R«r_å³*CÕK5Iu‡jƒê5´š©Ú"µjûÔ®ª¨ÓÔÕyêùê§ÔjÀ¦Á«4i´iŒijiúhŠ5÷h^ÑÑ¢k¹k%ji]ÐÖ¦j»j µ‹´/j¿`(3XŒdF £…1ª£¡ã«#Õ9¨Ó®3®k¤¦›£[«ûD¨ÇÔ‹Ó+ÒkÖÕ×Ö_ ¿Z¿Zÿ¡Á€i`°Û Õàƒ¡‘a„á&ÃÃ!##ŽQ¶QµÑccб›qšq…ñ=¬ Ó$Éd¯I‡)ljgš`ZfzÛ 6³7ší5뜋™ë8W4·bn·9Ùœeži^mÞgA·°È±h°x5O^ô¼óZç}³´³L¶Û;ªÝ»MvÍv_íì%ö5öÃú1åÝL3ˆ¹…yÝãèá¸Îñœã''{§ §SN9›;'9W9Í7š/˜x~¿‹® ×å K¯+Ã5Æõ€k¯›Ž×­Âí™»ž;ßýˆû Ë„•È:Îzåaé!ñ¨óøÀvb¯a_òDyúxæ{¶{)y…y•z=õÖõŽ÷®öõ±óYåsÉãëï»Ã·›£Éáq*9£~~küZüÉþ!þ¥þÏL$M à~ v.x¼Ð`¡haC äî |d”ôë"좠Ee‹ž[¯n ¡†,© yêº-ôQ˜q˜4¬9\>|Ixeø‡ψˆÞÈy‘k"oE©E ££qÑáÑG¢Ç{-Þµx`‰Ý’¼%]K–®\zc™Ú²äeç—Ë/ç.?ƒ‰‰ˆ©Šù äVpÇb9±å±£<6o7ï%ß_ĸ ƒq.q…qCñ.ñ;ã‡ÜŠF„la©ðu¢oâþÄIIG“&’#’kSð)1)gEJ¢$QKªVêÊÔN±™8Oܛ收+mTâ/9’¥/MoÌ !ƒQ›ÔXúƒ´/Ó5³,óãŠð§W*®­lË2ÍÚœ5˜íýó*ô*ÞªæÕ:«7¬î[ÃZsp-´6vmó:½u¹ëÖû¬?¶¸!iÃo9–9…9ï6FllÊÕÌ]ŸÛÿƒÏÕyry’¼îMΛöÿˆþQøcûf›Í{6Ëççß,°,(.ø²…·åæOV?•ü4±5nkû6ûmû¶c·‹¶wípÛq¬P±0»°ç‚õEŒ¢ü¢w»–ïºQl[¼7q·twoI@Iãý=Û÷|)M(½_æQV[®Q¾¹üÃ^þÞ;ûÜ÷Õì×Ü_°ÿóឃ>ë+ +Šaez~8üpëÏÌŸ+¨)8òõ¨èhï±àc-•••UUÛªájiõðñ%Ç;Nxžh¬1¯9XK¯-8 NJO¾ø%æ—®Sþ§šO3Oל18S^G­Ë¯‡ê³êGz£;ÏúmnrnªûÕâ×£çtΕW>¿íñBî…‰‹ÙÇ.‰/\Ž¿Üß¼¼ùÑ•È+÷Zµ´_õ¿zýš÷µ+­¬Ö‹×]®Ÿ»átãìMæÍ†[ö·êÛìÚê~³û­®Ý¾½þ¶ÃíÆÇަÎùî¸Ý¹|×óîµ{œ{·î/¼ßÙÖÕÓ½¤»·‡ß3ô ùÁ뇙Ç­ŒyœÿDáIñS§¿›ü^Ûkß{¾Ï³¯íYȳGý¼þ—¤ÿñe ÷9åyñ ö`åõйaïᎋ_ ¼¿ÉûSñÏòWƯÎüåþWÛhäèÀkÉë‰7[Þª¾=úÎö]óXÐØÓ÷)ïÇ?äTýxìóSëçˆÏƒã+¾à¾”|5ùÚôÍÿÛ㉔‰ 1WÂPˆÃqq¼9 % *2COÏÓSMLøO<=sO™=5H˜‹Ø—8‰¸ázD‰“#Q¨;€mld>3ûNÍ铆E¾XxNÒƒ<ø‡MÏðßõýÏ&UmÁ?ã¿…OûÉžÀ\eXIfMM*(‡i> – Û™ ¡·iTXtXML:com.adobe.xmp 1 2 1 2 1942 787 7‰Žß@IDATxìÝ ¸UU½ðÅ(ƒ€(¨ *¨˜â€i8 Iމ)•6¨={/¬g“Íef9&fi6X†ã#RKDMCœ ­qBÄQ.¼³6žÍÝçÜËårî=¿ý}dz¦½öZ¿½¹øñ¿kí+sGp @€ @€ @€ @€5´\C¹b @€ @€ @€ ,{ @€ @€ @€ @ ¬€ÀrY• @€ @€ @€ °ì @€ @€ @€ @€²ËeyT @€ @€ @€ @€€À²g€ @€ @€ @€Ê ,—åQI€ @€ @€ @€Ëž @€ @€ @€(+ °\–G% @€ @€ @€,{ @€ @€ @€ @ ¬€ÀrY• @€ @€ @€ °ì @€ @€ @€ @€²ËeyT @€ @€ @€ @€€À²g€ @€ @€ @€Ê ,—åQI€ @€ @€ @€Ëž @€ @€ @€(+ °\–G% @€ @€ @€,{ @€ @€ @€ @ ¬€ÀrY• @€ @€ @€ °ì @€ @€ @€ @€²ËeyT @€ @€ @€ @€€À²g€ @€ @€ @€Ê ,—åQI€ @€ @€ @€Ëž @€ @€ @€(+ °\–G% @€ @€ @€,{ @€ @€ @€ @ ¬€ÀrY• @€ @€ @€ °ì @€ @€ @€ @€²ËeyT @€ @€ @€ @€€À²g€ @€ @€ @€Ê ,—åQI€ @€ @€ @€Ëž @€ @€ @€(+ °\–G% @€ @€ @€,{ @€ @€ @€ @ ¬€ÀrY• @€ @€ @€ °ì @€ @€ @€ @€²ËeyT @€ @€ @€ @€€À²g€ @€ @€ @€Ê ,—åQI€ @€ @€ @€Ëž @€ @€ @€(+ °\–G% @€ @€ @€,{ @€ @€ @€ @ ¬€ÀrY• @€ @€ @€ °ì @€ @€ @€ @€²ËeyT @€ @€ @€ @€€À²g€ @€ @€ @€Ê ,—åQI€ @€ @€ @€Ëž @€ @€ @€(+ °\–G% @€ @€ @€,{ @€ @€ @€ @ ¬€ÀrY• @€ @€ @€ °ì @€ @€ @€ @€²ËeyT @€ @€ @€ @€€À²g€ @€ @€ @€Ê ,—åQI€ @€ @€ @€Ëž @€ @€ @€(+ °\–G% @€ @€ @€,{ @€ @€ @€ @ ¬€ÀrY• @€ @€ @€ °ì @€ @€ @€ @€²ËeyT @€ @€ @€ @€@k м/]æ¿·4,X¸4Ì_¸,,[^Ó¼'lv4¨@ûMZ‡N7 ;¶M¾7iÓªAû× @€ @€MC@`¹iÜ'£$@€À: Ä@ò´o‡grŸ§sŸ˜w @ ¡6ïÜ.ìÒ§k°}×Ð?÷iÑ¢¡zÖ @€ @€•,Ðbeî¨ä¬›À33æ„ûŸž}yÖS MëVa·º†¡ûô ÛnÕi={s: @€ @€•, °\ÉwÇØ °/½>/ÜûÏ—ÂÔé³3­7ïÜ!ôÙv‹ÐmóN¡Cû¶¡cî¿Û¶±YEJ†²K–¾-^æ>‹–, ¯Ïžf¾6'É×>qÿݶ C÷í¶ìÒ¾v±4 @€ @€ÍD@`¹™ÜHÓ @ újjV„›ïšþùÌéä;ulvÝq›Ðg›-–›uLË% ÐÐoäÌ/½67üû™WCÍŠi÷‡æV/5¤_š— @€ @€ @ y,7ûhT™À¬¹ ÃõwN ¯ÎšŸÌ|“¶­Ãž»ô {íÒ³Ê$L—-W1?þô+I€9?–í¸U8åÈÝB›Ö-óE¾  @€ @€hâËMü>Õ'ðô‹oç‚ÊO…ÅK—'“ߣÿvaŸ}r[\·ª> 3&@ bÞ¿(<üÄŒðâko'cÚ¶[§0òÈ]C÷®›VÌ „ @€ @€ú ,×ßΙhtÇŸy3ŒÿTz݃öîî´mš— @€ÀÆxè‰éá‰i¯&Ãh—ÛMᬅí¶î¼±‡åú @€ @€¬§€ý ×Ðéh,—ߘnÈmv›´ Gd  rcá»ë,pÀž}ÃÁ{_²ly¸~ÂÔt‡…uîDC @€ @€*N@`¹ân‰ @ X`áâeIpfÅÊ•Iåðƒv ½¶Ù¢¸¡T€Àn;m¼s2’Ys†Ưú¥˜ š! @€ @€ POåzÂ9)ƒ2³ßY”\ò°ýû‡îݺ4æå]‹uз{Øk—^ÉySgÌ·ýã¹:÷á @€ @€*G@`¹rî…‘ @ ¤À£O¾¦Íœ“Ôí=°wØyû­K¶SH€JØoíÃŽ½º%ÚøÄËáå7çUÚ‡ @€ @€uX^G(Í °±þ>ù¥äÒÝ»uƒöÙXÃp]ÔKàà}w mÚ´Jν÷ƒŸgõêÈI @€ @€lTåÊïâ(/0qÊÌ0gÞâ¤Ñ^Vm)[þ µ¨,MÚ¶N·Ä~ò…Ùá¹v`¨¬Q  @€ @€kX^›zl$å5+B~µrï[„>Ûm¹‘Fâ²X?A»ô ›vØ$éäÞÇ^Z¿ÎœM€ @€ @€ÀFXÞ(ì.J€µ <=ýí°pñûIÃA»Z­¼v1-¨T-Z„\ŽÇ󝾿¼»j'†J¯q @€ @€ P, °\l¢„!0mÆÛÉ8:oÚ.l³U—Š“A @ ¾;ôì–žšÿù–H @€ @€ PñË‹ jxú¥UåÞÛÚ»ZŸó&М:¶ozt[õK2ËÍéΚ  @€ @€@µ,WË6Oš”ÀôÜV±ï-Z–Œ¹OîýÊ4>Û¬úyöÌÌ9aé²åÍaJæ@€ @€ @ j–«æV›(MIàÙï%ÃmÙ²EèõA ¦)ßX  PJ öϳ7Þ^Xª‰2 @€ @€*T@`¹BoŒa PÝ .M:´k[ÝfO€@³èÛ;äÎåó¾  @€ @€¨låʾ?FG€@• Ì_¸jìÚA˜*¥0mš‘@üe™-Z$3šÿÁ/Ð4£é™  @€ @€f- °Ü¬o¯É ÐTò+ù:Z±ÜTo¡q °ü/Ìäf Í @€ @€ PaËvC ‡Q`Á’÷ˆ|† š‹@~‹ÿ…‹WíÌÐ\æe @€ @€š»€Àrs¿ÃæG€ @€ @€ @€õX^O@§ @€ @€ @€ @ ¹ ,7÷;l~ @€ @€ @€XOÖëy¾Ó  @`#ÌŸ÷nxýµWÂüùï†Nº„»î¾Fá’ë"0ý…gCÍòåiÓm¶í6íÔ9ÍoˆÄÌÓÃã“NºnѲerðaaÒ}w¥—:äˆá¡K—ÍÓ¼ @€ @€X›€ÀòÚ„Ô @ ‚&Þ{gøÚ¨ÓÜ·ßÊŒjú›Ë2y™Ê6d`f0^þ›ðñOœš)kÈÌò÷ß_üüIá™§ŸLº|ÀÁá°#>¾ÿ­³ÃÂ÷$e'ül¸àÒ«ò²ú"@€ @€ @€f.`+ìf~ƒMæ#ðȃÿŸyLQP¹ùÌÐLBà׿¼$ *Çþ¾öí'+¤ãwþ¸ùú߇ïÿ{>ë› @€ @€¬U@`y­D @ 2Æ\k…ie܉ÊÅK/¾.¹à{é÷ÙoHØs¯ÁI>®’î¸i§´îëgŸ–,Yœæ% @€ @€ @€@9år:ê PA/N.3šî=¶ ?úéáÊßÜ”)—©,]îúí4 ýl¾Å–l€¿¸ìüLßgþ÷9i¾C‡Žás_8;Í¿ùÆká–®Mó @€ @€ @ œ€w,—ÓQG€ ˜ýÖ¬Ìh>sÆÿ„O~æ ™²ÆÊÔ,_Zµ®ß_!555¡U«V5Ô vuÇmw?º^cXWëž›ÆÝ2&½VÏÞÛ‡ƒ92ÍÇÄÈSÏW\²zKì+.9?œxòi¡}û™v2 @€ @€ @ P ~QÂ^ä  @`ƒ,]¼8\yÙOÂÊ•+‹Þ­üФûÂÒ%KB‹-™ÿóÕЦMÛpþ÷¿Þ™ûv:–#‡Ÿ;òè4óæ½ÎûÎÿfʾô¿ß ½·ï›”Íœ1=üúÊ‹ÓúN7 ßüÞaâ½w†k~yYøÏ¿þ¾· ÄÓ»Ü#Y»ßIÛ&âØo{CxhÒ½aê“O¤ïÿí¿ËÀ?#O=3ìµï…§­5?úâó¬7_OÛ}ò´/„Λm®þÅEaÒ?î¯Ìœvßcï0ô°†CøXn¬{&mŸ˜òh˜ð·¿„Gš˜Ì%Žaßý>¾üï‡.]6Oû+LÜqûØpÿ½B à>÷ìÓ‰Á–]· }vèÏü°ív½ O çþïÂûï/KËOùôé|W¬X¾"lÏ_ùÆÂÜ9o‡_þYî=È÷&÷=na½Ën q;ëF|ªdPÿ¿½2ßEò}܉Ÿ, àoµu0äàÃÂïIÚÌyû­pÇmÿ—ô›9Y† @€ @€´ÈýƒÿÊ‚2YØÈ—]ÿXxuÖü°Íæ­ÂW?{ÈZGóijo…Î]6 ûî¶]&ýå¯}/|éœïdÎýµW‡÷ZDÎWÜtë}aïÁ&ÙÉ>N>võ5cPókßþqøÁ¹«·QΟ—ÿŽ[,û‡å³é÷Ü9³Ã¹çüW¸çÎÛÓ²R‰¸²öªko mÛnRªºdÙaîfL>­ûúwÎWþü§IÀ7-¬•˜pÿ¿Ã¯¿>sòðZ¥«“1Pþûþvê¿ëêÂ\*â¿÷/…¿þåæLy©Ì¯~wKvÔ±™ª¾ÝÛfò^þ›4W#ï´]vµðO/ûuøæWÎÌœS;ƒå7Ýv_Æ*ö³gÿ­3s¿ñÖ{Ã>ƒ‡Ô>5IÿöªŸ‡ŸüàëiùÐÃŽ ¿ùÓ_Ò¼ -pó‡Ùs„ýnNò´3Ã&íÚÕoˆ‚¸môš¶­¾}ÜM™K^ÿ‡_'ÛQg s™íûîúíT¼JñÆë~“n“]xN]òq{ê¸ÊºðˆÛ>ç¸:9¶+<âöØÏ<ýdZ·îŽ×ÚGì;š8å3É\j×ÅôÃü£°¨^ù8ÆÁ\r.7üñšLŸw¿-“Û]Ç­ÑK…+²c›ûォTSe @€ @€ @ XN)$ Pyí;nοè—áü‹•¼Ó¸öc`3–Ÿ÷³_„M6ÙðåÛï~,üíÞ)áæÛþ‘|oŸöT:¼ùóÞ —]øÃ4ŸOÜqßãáž§†¸-u{á·á^ŸãÚÿ{êÕðŸæmžï÷×|bFÒîóÿU¼ByúóÏ䛆?>9Mçw?ðTb·¬ÿ'ŠîË“¹Åë{D›8Æëÿ|wx`Êôä]ÔµûŒAòE ßK‹¦<öPšŽ‰ø.å5-[¶ ={oŸ©Ž«  @€ @€ @ œ€Àr9u |çG‡]î‘jÄ•ËtHš‰é/<›æŸ~ê_E[3Ç@ìÎvKÛœ4ò³EÎǺ?­¯k⠡Ç?rxzÚ§>ó…4OväÑáÐa«ß±üéÏž•¯J¿,˜Ÿ¦c€6Y1Wç>ß?ÿ²°u÷ÕAÛE‹†M;uNÛÇDíó3똉㫽B=¾;û%V¬¿ùæëi¯½úršŽ‰®Ý¶Îä 3…ç7sïžv @€ @€ @€r­ËUª#@€Q`à‡ö*‚Ø©ÿná®;n-*¯¼üRQù΢ãÊÙ›nýGX¸pAÚ¶SA6­X‡DáÏ¥‚«» Ü3ÓÓ¶={gò1³lÙÒ´,¢ã'_>ù‘µ×\^}efxú©‡¸uvC{ì5¸¨Ë¾ýv.*[QS“”½ÿþ²P{›ïX¸e×nEíkl]°¢9ÎÇA€ @€ @€Ê ,—ÓQG€‰@ŸúI´oß¾¨,_ðòK/æ“éw÷îÛ¤é|bÕêßÕ+€óåõùÞ´Ä{•ã{”k]»l¾E¦ë-Zdò1SX·õþÙyç†ÛÿrsÑ*좓  o¿þE½´oß¡¨,7ФìíÙ«ßoT*¨ž¯‹ß…õ…+žk·•&@€ @€ @€Q@`Ùs@€*X±bEg\2°Y¦—Yµ¶iŽÍâû˜[µnzå,]º$ü×gGl•ÉkâkßaÍûÌ9+W&Ù–-ZfŠc¦]»ò}´ïбè @€ @€ @ œ@ñ¿F—k­Žš”@M‰ òë¯eßÇ»!&Ô}›í2Ý.|oAX¼xQ¦¬)d~òý¯•ãû¥¿üµï…1cï ?;+œvúÿlÔ©tí¶UÑõçÍ{§¨¬vÁ;sß® ½zoŸÉË @€ @€ @€B¦·|¬pò tîÒ%³õóÌ/¤uùÄ‹Ï?›On°ïÞ}v(êûÕÜ{—wÜy—LùçFžödZ6ì£Ç†ïÿäçi~c$jo…}Ï„Û3C8~Ä'ÃE£—Ù.»Ô¶ß™“6p&®ïÞcÛðæ¯¥Wz÷9iºTbnA`¹§Àr)&e @€ @€ PKÀŠåZ’hê[lÙ-3…'¦<âvÎù#¾/ø÷×\‘Ïn°ïRÊ«qQæz/N.L¼÷Î$ ƒ¢ñSjõmæ¤FÌ,Zø^&X/½ÿ¡™ ò»ïÎ =2©GUúR…Þï¾³¶ËÙÀsmz–îX) @€ @€ø@ÀŠeš‘@Ÿíû…)=”Îè•™3™§žŽ9áä0wÎÛáöq7…ž›–Öo¨Ä^{ï¶ï»c˜1ýùôãn:tÜ4~ä1aåÊᲟý0­Ë'=âè|r£}·-’ko²I»¢1Ä9ủ¡m›¶áŸ=.¿øÇ!nó]û¨Ï;¬kŸ_Ÿt\ >ù‘ÒS_š±Ú=-ü ±2÷næ—^Ì®dß¡ßN…Íä  @€ @€ @€@F@`9Ã!C€¦-0ôð£ÂØ›þ˜™Äï ñÓ˜GÜžùœsÏ ÿsúəˎ¹öê?¥ŽâÔß_¼±üVØq»ï±wøÏ¿þ™éáî »m¿Yè¸i§¢€r¾Qa 9_¾!¿?òèpý~^âÁûï 555¡U«ViY>1sÆôÌvé±ü#‡™¯öM€ @€ @€J Ø »$‹B4M#Ž:.Äm¹c×{–«n°º8–§|fúûØq'…þtô:µmÌF_ýÖy%/W;x\ Û¿={VÉó6TaÜ¢;»óGßóÏ>Ïf¾ãJëÚǃËmA¾uí"i#°²q.ã* @€ @€4Œ€ÀrÃ8ê…\ MÛ¶™kĵ…GË–-×\Fžvf&Иo7ê«ß 7ßþ¢ºx^þhÕ²x•k‹Zõi»‚Õ°íÚµÏW%߱ϟ^öëpé•È”fÎÎé²_þ1´oß¡°ªNù–ã‰'·iÓ&ÓGíyf*jeò+–cÑ.ùŵE^±.nõýûn\Z¼ûê_\›”²oýÞ¼¿üÒ‹!¾o·[·î¡÷ö}KHcjË–- q æž&÷~ßçC÷îÛ†»}(ôÛ±h]ümŒñÔõï-˜Ÿl‰ç°Ù[†½÷= tÛª{]»Ù í¼ÿïáÔ“>š^cŸý†„ÿroš‰Å‹…Á{f¶ñž<õµ°Å–Ý2ídlH›ï|<Ìž» ì¿Û¶áÄÃlÈKé› @€ @€(¿lª/¤+h\¸ò6“ãgcmÛnvÜy—ä³±ÇRŸëoÚ©s8àÇ$ŸúœßçÄí°ã¶ÜÏ<ýdr¹É<žöTØyÀnéåï¸íÿ2A帲]P9å‘ @€ @€ @€2«÷>-ÓH PÙq›ï/íû™AþéÚ«ÒüŠ+Â﮾<ÍÇÄ™ÿýÕL^† @€ @€¬I@`yM2Ê  @€@8ìÈ£ÃAC‡¥£¾þ¿¯Ìœ‘äï¹óöt5s,øÒ9ß ={õIêü‡ @€ @€¬M@`ymBê  @€@ˆÛŸŸwá•™wi_qéù¡fùòpáùßJgÑo§á¬Q_Oó @€ @€ @`mÞ±¼6!õ @  l׳w¸ðç¿ Wÿâ¢dÔ/¾ðlø÷¿þ:uêvßcï¤ì¢Ñ¿ ›lÒ® ÍÊP  @€ @€ @`c ,oì;àú @ ŽüØñ!~jãî|¨vVš @€ @€ÔIÀVØuâÒ˜ @€ @€ @€Õ' °\}÷ÜŒ  @€ @€ @€ P'å:qiL€ @€ @€ @€êX®¾{nÆ @€ @€ @€¨“€Àr¸4&@€ @€ @€ @€@õ ,Wß=7c @€ @€ @€ÔI@`¹N\ @€ @€ @€ @ ú–«ïž›1 @€ @€ @€ê$ °\'.  @€ @€ @€ P}ËÕwÏ͘ @€ @€ @€uX®—Æ @€ @€ @€¨>åê»çfL€ @€ @€ @€: ,׉Kc @€ @€ @€TŸ€ÀrõÝs3&@€ @€ @€ @€@–ëÄ¥1 @€ @€ @€ªO@`¹úî¹ @€ @€ @€ @ NËuâÒ˜ @€ @€ @€Õ' °\}÷ÜŒ  @€ @€ @€ P'å:qiL€ @€ @€ @€êX®¾{nÆ @€ @€ @€¨“€Àr¸4&@€ @€ @€ @€@õ ,Wß=7c @€ @€ @€ÔI@`¹N\ @€ @€ @€ @ ú–«ïž›1 @€ @€ @€ê$ °\'.  @€ @€ @€ P}ËÕwÏ͘ @€ @€ @€uX®—Æ @€ @€ @€¨>åê»çfL€ @€ @€ @€: ,׉Kc @€ @€ @€TŸ€ÀrõÝs3&@€ @€ @€ @€@–ëÄ¥1 @€ @€ @€ªO@`¹úî¹ @€ @€ @€ @ NËuâÒ˜ @€ @€ @€Õ' °\}÷ÜŒ  @€ @€ @€ P'å:qiL€ @€ @€ @€êX®¾{nÆ @€ @€ @€¨“€Àr¸4&@€ @€ @€ @€@õ ,Wß=7c @€ @€ @€ÔI@`¹N\ @€ @€ @€ @ úZWߔ͘C`Ñ¢…aEMMæR›´kÚ´i›)k.™÷̯÷TÚ·ïZµnþ%×,_/^´V§ö:†V­Z­µ]C4˜þ³!Ž+l³mϰi§ÎùìFýþ磆WfÎHưe·n9“Öá­7ßHòÛl×3 >àà:>'@€ @€ @ ºšÿ¿bW×ý4[T„À’%‹Ã~»÷ ß[ÏI#?.¸ôêLYsȼ3÷í°÷.ÛÔ{*ç_ôËpò§O¯÷ùMåÄ ão _:ã”un¿„vÞ%|êsg…Áû´NçԧѰ!3§]xùoÂÇ?qj¦lcd^yù¥ð‰c‡¦—þò×¾Úå~á§?úfZv÷ƒO…úî”æ% @€ @€ °!–7¤®¾  P¥÷ß{WQP9RÜ|ýïÃ÷òóЮ]û .óâôçÂ×G}>½NËÜ Ø?ÜtGˆ«ƒúX¹råzu¹bÅŠõ:¿ÔÉ9ÿR×/U¶²ó|á¹i!~î¸}l8ìȣ忸6tÜ´S©n›DÙÈË–.IÇúó. { Ú7Í×NÄçé;_ûbZçý¹/œâ3|Í// sÞ~+©ûæWÎ 7Œû{£­îN$A€ @€ @€@U ,Wåm7ilX[ÿ|ý/ƒÎÃŽ:võ U·¦~bÊ£™î–¿ÿ~>¦¹æÆÊ4§ùßsçíág?þVøÑO¯ØXœë}ÝGš˜écÞ»s3ùÚ™[ÇÞ˜xOZtÆY_Iƒêg|ñ+¹UËç&uS{(ÜrõáäO­þŠô$  @€ @€4°@ËîOw Påóæ½îüë¸5*” :¯ñ¤&ZWš®Ë'¾S¸Z-»nzöÞ>ý¬Éa̵W‡Hmèc×{†¸ívþ³ù[6ô%êÔßûï/ ÿíÌ9§œzFš?铟KÓ1qéO¿¿Nï­Îœ$C€ @€ @€zX±\4§ @€ÀšîºãÖ5WæjbÐ9Ÿ»tÙ¼l»]9wÎì·$ŽÏúßÍmuü™3¾TŸS×zNÍòå¡UëÆù+|C_ë—¿½)ì=øÀt΋/ =|øÝÕ£3«vcƒÉ>öÚ÷€´m©Ä¢…ï…ùóç…­¶îZ¶\ûïÏÝvwvU{©>Ë•5´ÏØ›® o¾ñZzÉ‘§ºvÛ:ÍÇ?7q[ìß]}yR·Å¾%·Åü©Ÿÿï´ @€ @€6„@ãü«ô†¹>  @ "ÆÝ2f­ãŠ[ü§–lW¥ÞpÝ5i]Ë–­Â—\UH{ÓÃÃÜ—¶ÛyÀnáŒ/ž^xþ™pgî½¼¯¾23­Ë'®ºâ¢Ü;–Û‡}öpp¾8ùŽÍß]õó0eò#á©ÿ<ž¾Ç6®8ÞkŸýà½÷ §ñ7È;škdæŒéá×W^œuê¼Yøæ÷.ï½3÷~ÝËÂþõÏäýÕÝ{lv¸GdÜïÀ¤íë2ÿ8çï}ãK¹úªw<·hÑ2üø¢+Ãk¯¾~þ³„Gº?qøí˜ÛÂ_ÿrSz˜8â¨ãÃá=&S3÷Þý·pÇmÿ—)ïÞl³-2ekÊÄw`|È‘áC{îöÐ#Ó,¾s¹Ô1îæ?%×}2wß^™9#mƒÐ»î¶Gø|n+éízöNËk'Îýß/„¸J8œòé3Òàu|÷õ÷ÏVÔÔä«ÃW¾ñƒ0wÎÛáW£¼ÿÞÄ'>#»ìö¡ä™>aħÒguÑ¢…á÷¹p©wp½ñáÉM =¶í™ù³pÕ¦×Љ#‡ŸÉçËòå˜ÿÅe„O}ö¬u ¤u¦€ @€ @€ë( °¼ŽPš @€ÀÚ^í•Pø.Ù¸r÷——ÿ, ÔÆ^bðyMå—f¼Ô×¾Z v¶ Ù¿²ÿç#™vûš–Ÿ}úÉpÙ…?¬}zšÎí¾xö73å§?¾tÆ)á™Ü¹…ÇÂ÷„ûï»+ùüõÖ[•¿¹1Ù6¹°]CåßzëpãŸ~›vƒ–Ûöì~pîÙiYLÄU­ñó÷»þ–—¿ýË’úºÌé’ÅáÏ7_—é÷¸G†SOúh¦¬knÕvá/ ¼ùÆë%Ë7^÷›dLùb¼s.8^×c³Í·L¶Ç®(~éÅ2Ý,È­LþöWÏ +dçÅ_RˆŸ±¹9^zåÂaG|,_•~ßœ[í[ûˆÏQ~UôÊ\`ùú?üºvuØc¯Áá›_93SŸ‘É<|bû›n»/´m»IX0o^¸4 /uÄ1ÇÏî{ìþYˆÏ_íùÆóå~©¡ðˆA÷ÚG\µüä¿§$ÁøÚåÒ @€ @€hHµïÙWÓ4k•ªq²ÃŽ:.ûñS2óŽ+ko÷›©läÌôž ‡¸[É ráPâŠÙ#úPxvÚS…U,ƒ–…Aå‹ÅÕ«Þÿ÷ÂâzåÏúÜIEçuÙlópè°á™òxã–æµ%¹@u t×>N8éÓõ^IW×>Z¶j•f—-[Žÿèk *§ s‰hø…ÓNÿwãj×+]T.ì$®(¿òç?-,^§ü=wÞ–iWnÇÜ…Gë6m‘Ë®d¾{|öÜÂsä  @€ @€ °¾Ëë+è|Hn¹áÚ4q5æ6¹­~;òèLyÌŒÏmW½!Žž½·ÇødrðaEÝuôÇ“º]î™Ö]øão¥é|"®ŽcŽŸ˜.<~v^ñ9…mÎûî9¡o÷¶kýüâÒó O]c¾ÿ.ÓÕ´…n·j«êºÎ¿°Ÿ„-usBö—b›‡'ý#Óô‰Ü*òÂã¨cN,,Z§|\!]8–Þ}ú¦çÆ•Á3¦?Ÿæó‰ƒ† ñ>G‡ÂãGßùßðÞ‚ù…ÅõÊÇ•Øq;õRÏÇ \µ•{‡ŽC ¬Çç±ðˆ«¢cyíUÔ·ÿåæL³}ö;0“¯Ù{ßlÝ_oÍž[»­4 @€ @€B »¯hCô¨¨J¸oá;p>þ‰ÅÞûàj càð³gŽjp«̾øŠß'ï"~`â=™þ’{Ws§Î]Ò²É>âûžk1XøËßÝ”¾øÝwæ„/~þäÌßñ}ÇMº7ðáCjŸZ¯tMn»åu9n¿û±°KîÊñˆÖ'ó‘Là5¿Šº.ó/wݸUùŽ;]»u]·Ú:røQEÍãû”k¯œô¬÷ö}w vݽè¼ÚäVZÏž=+-záÙ§ÃWrù~®z/v\)]¸Åô–¹íºÿxóøƒïñ¨Y¾<üô¼sCíwÇçï·¹ÕÝgõ»éõê“8ÿâ_…“?õùäÔùóÞ §hf¼qkêE ßKžµ‹F¯ÚÖ¼p+ñÿþò7“wIç¯ÇVøçg«­³ï˜Î·ßݶê^;›l¡=wÎì°Å–Ý2å2 @€ @€h(+–JR?¨r¿åÞ?\xvĪ•Ê­Z·à V®N}ò‰ðÂóÏžÒ¨ù»î¸µèz^þ›4¨+ã»~zéÕEí&üí/Eeªà;?º8 *ÇkÄàéeƒÚqKï†:n¹}bˆg|ñœdUmÜŽ¹CÇMC|ÿrí#€c7üý®¿æ“É÷šÞ£]»Ñ—ü8üÏé'§ŸŸ_ô£L¶vÛ!žd'?ü@&¨ 㻼óAå˜ÏÜ7¿{AÑÊåq·ü)V×ûˆ[‚çƒÊ±“Î]6 Ÿø È\»Ó7ß|½vv­éÙo­®ço±e×|²è»TÝ[³Þ,j§€ @€ @€ % °ÜP’ú!@€@ ÔÔÔ„›Æü.#·›îÕg‡´lØQǦé|âŽÁè|]c|®ï´Ý®gï¢KÇyn­ý|î}Ëk;â6Ékû´oß~m݄ګ¨ÍNýw+*kˆ‚/üÏ× }ö/ÙÕǎ˾9®²ýÏ¿§$mã;³ =:º~Û`—ºø×ܶî¾jï‹Ó‹ƒèqûëÂ#—?ñÉÏeŠ_™9#Ä÷3×÷Øc¯ÁE§öí·sQÙŠÜŸ‰º³f¢Ë­>.U÷V‰>ê2m  @€ @€ PNÀVØåtÔ @€À: L™üPˆÛÿÖ>~hPxqúsiÑÖ[o“¦ó‰ŒþÒ9ß -Z´È5ê÷Ô'ÿ•¹^ïíW¿Ã7S‘Ë$Aò‰«KŸúÏã«3%RqígÎøR‰šºõÙ¡_ÑIë.:i vËÝ·5thÑ–æ÷ßwWØ3l}0·¥uí#nÉ]γvÛréý‡ _þÚ÷ÂÞƒW¿Sø…ç²+Ýã–Û1ˆ\êèÙ«ø]Ë/ç‚Ëývì_ªùZËúö+>/®è.:êøLÏ.±Ú¸Tð8+–ó¾  @€ @€h,Òÿ ÛXWwh·»©h7þé·!~Êq•ë¿, L–kWª®®+BKõQ / üàĺ¸ZwåÊ•/¼v©¹4TY—ÜÖÎk:Ú¶Ý$â'ß®½*mr×IÞY̵ãOúTíìÓÇœpr.½:pÞºU«ä=ÁÛöì¶Ë…K­^°`^¦¿6ÍäkgÚ•úÆ÷×÷hßaí+Ì“¾sÏF]ŽR¿\Ñ®]»5vÑ®]ñ8Jõ±ÆT @€ @€ @ ŽËuÓœ²K—. ·Ž½![X‡\|7s\ñZîˆ[m3_z±°¨Îù¸ªö?ÿúgzÞ¯¿’¦ o¼–­‹ïóm޼V¹Àn¹ãcÇŸ” ,?óô“áåܽ¸ïžñ™ÓŽ8êøL~M™Ožö…Ìjä5µ«]¾CßìÖÓ/Íx¡vu&=+÷Ë …G¯ÞÅ«˜ Û4v¾{m‹.9þ¼Ðm«îEå± Ö¥ú(l#O€ @€ @€ú xÇr}åœG€‰À¤ûîqõn}›¯ÿ}Xþþûééí;tLÓùÄëAÝX¾¶­¨óç–ûÞqç]2ÕÞoˆòÂcÉ’Åáч'eŠwê¿k&_-™A{ï¶ìºUfº—_ü£Ì3·¯Î¿9Ó°2;ôÛ)ÓS|þžöT¦,Ÿyàþ{òÉä;¾óz³Í·Ì”UBf«îÅ[ÅÏ{gî‡6ÿÝwŠê¶.œ.j¤€ @€ @€õX®'œÓ @`•À­c¯/¢rðaáàCŽ,ùéY°Z4~ði¥¶b~⟤õ1qË ×f™™Ê2™eË–fjwßsïL>ný§ß¯Þæ9_yÝï~YâÒ{å«K~/Z´0cœßÚ>‹/*ÙGCο>ýÇÍ#NùLæÔ¿ü_ö8îÄ‘™ú†Îì:pÏ¢.~ÑCÍòå™ò'¦<îºãÖLÙ~”Éo¬ÌÒ%Ùg±T ~Þ¼âàq~¼ï¾[tÞzëùjß @€ @€hp[a78©  P=1ðuÇíc3Þ¾ïŽá7Ý‘)«¹{ümá¿>{bí¢pÛŸo þÈáIYÏÜ{u o|ùŒðÖ¬7Bmz†ÿ<19ü1è­Ïñ·Ûþ/ðáCBÜÊθrõãŸ85Œ¾øÇ™ ñO~ðõðêË/…ÃŽ<:¹Ä=wÞ^t½¸b÷äO}¾ì.¹à{!~ÖåˆcùÏ sÖ¥ézµ)œ};~̉áª+.\ãéy»56XÏŠ¸ ùQG<óìÅòç>yLøÔgþ+l¾Å–É»»/øá7Š®4ê«ß-*Û÷L¸=ôÙ¡_î}Ò]C×n[‡øþêøK¯Ìœ‘çÕWf¦éÂDa]|&;uîRØLž @€ @€ &`ÅrƒQêˆÕ'ƒ®…Çñ#>UX”ÉxÐ!™|ÌüùæëB\á^}v18]xÄ íW¿ôÙ¢ oa»|¾Ôûfø­/‡#>¼{.(zQÒ¬}ûáÜïÿ,Jú×§žôÑäS*ˆýÕo:tÜ4m_‰‰u™}ǽËÀ=JÞ£Ø_ *o¶ÙõízÏûê·~\Ôö‰÷$¿´ð‰c‡†RA帒z·Ý×…ÏôØ›þ>ú‘=ß^ý.ê{Rf(=t&_;óØÃÙºc?~Jíji @€ @€4¸€Àrƒ“êÕ#0î–1E“=bøê@YQe® dK­hýÇßïL›_~ÕŸ’ÅiA‰D\µZîØ*·-ð^ûP®IRwÌ '‡º¬býâÙß ?éÓkíwc7X×ù×wœ'¬ÁàØoØm°óãí½}ßð«ßݲÖç$ß~èaG…ïžwI>ÛèßGòZ¯yäDzv&ÞwWX±bEÉóîϽۼöqäZþÜÕn+M€ @€ @€ú,×GÍ9 â;^~ྌD¿„~;öÏ”•ÊyT6€ÛÜ=þÖ´i|‡îﯿ=” Dzqw>N9õŒ´}L´iÓ&“™ýôŠ0ì¨c‹ÊkÄwŸÛyÌØ»BÜNxMG¬ûãÍãÃ9çþ(´j}“DË\ u´jYÜW‹–Å]Çq×>Úµk_;›¤×6ÿ’ýÌ­¨Ó †3¢dÕG=²dy,,åTh¹Æ“KTÄ{{Ç}¯õ¾ý˯ÿøç°Ùæ[–è%[ÔªUöÞfkC(U_ʱp®Ÿ<í̉¡ÜŸûÚï ïæ~þÙ§‹N™9czfûöølî±×à¢v  @€ @€ Ð-V掆ìP_ °þ—]ÿXxuÖü°k¿á#ûî´þ6á-|/<ýÔ¿Ãûï/Ëm“Ý7÷žåíBËÖrSœ;gv.7;¼¿lYhÛ®]î¶[•Ü®9þ•8ëÍ×ÃôçŸIz1ŸËsó¸µt‹-Ê]¦bëÖuþuÀ{nÞ|ãµô´øÎê /ÿMšoÌDüE‡Ÿ6<— ľûΜ°CßC¿„Þ½w(úE€ÆWáµ–-[š{ÆÞ æ½Ú´m›¼¹ö¶å?¿èGáŠKVoóýå¯}/|éœïdºùõ•‡Ÿ÷­´ìŒ/ž¾ù½ Ò|¥'n¾óñ0{î‚°ÿnÛ†PéÃ5> @€ @€>Xö( @ –+ð¦RFàÅéÏ…ÃÜ-SWtxС™2™º Ä ó{ôNOŠ«‘|âÅÜŠü¶IYÍòåá }vÌô'N~>l×sõ9éÉšX®ÐcX @€ @€Ö"P¼·æZNPM€T§ÀâÅ‹Âßn½%\÷û_…3OÍng·%vÏð@IDATpÞïÀT'LÎzëî=Âç¾pvÚãœ·ß þö—4ß=ã3A呹-¶›RP9ˆ @€ @€MN@`¹ÉÝ2&@€Gàõ×^ £¾ðÉðƒsÏ3¦?ŸD †¾÷9Ó@fþû+熎›vJÛÿü¢†¸R¹¦¦&\váÒòØ&¾ÜA€ @€ @€ÆXn e× @€ÍX ¾#xÄ)ŸiÆ3lÜ©m¶Ùá’+~Ÿ^4ñoûóa Ï<ýdZþ“‹•{_øÖi^‚ @€ @€R õ†ì\ß @€@ó8hè°ðÝ_Ú·ïм'Úȳ;ü£Ç„/žýÍðÀÄ{’+?öȤжí&a÷=öNòûì7$|츓yT.G€ @€ @€@5 ,WóÝ7w PÞ½ww=ðdxÙ²°i§Î!¾¸M›¶uèAÓºœsîBü8 @€ @€ P Ë•pŒ­[¶HJj–¯(¨‘%°ñZ·iúöÛyã À•›…@|Wtà²H`9Oâ›f PS³",]¶<™IçMWýM3˜–) @€ @€¨ 媸Í&I€@SèôÁŠåE¬ìkjã7^”ȯVŽuùŸs¥Ú)#@€ @€ @ ò–+ïžB~+ì%Kßï-ZJ„ÍB`î¼…é<ò;3¤ @€ @€T´€ÀrE߃#@ ZúõÜ<úÌ׿¦i 4e™¯¯úyÖ%· ö6Ý:5å©; @€ @€ªX®º[nÂ4-º´½¶îœ õå×ç4…!#Ö*ðÒ?ÏôéºÖ¶ @€ @€ PYË•u?Œ†©@ÿíW^^Ê­ð«Y±"-— @€@S˜õöüðÞÂU[ûøàç[Sœ‡1 @€ @€¨Våj½óæM€@Å ä/+V® SŸ£âÇk€('ðô‹o&Õ­Zµv°b¹œ•: @€ @€•( °\‰wŘ èÕ½sØ©ç‰Å”§_+V¬äB€&)0{î{áéVý‚ÌÝ{†V-[4Éy4 @€ @€jX®æ»oîT¼À!ûöIƸhñ²ðøÔ—+~¼H€RùŸ_­[· ùŸk¥Ú)#@€ @€ @ r–+÷ÞÂŽ½¶»÷Û*‘˜2í•°`á*hR¯¼ñNxá•ÙɘÝ»OØ´CÛ&5~ƒ%@€ @€ @`•€À²'._Ý·|yM¸ç¡g*|´†G€Õó,w?¼êçV§\@9ÿólu ) @€ @€šŠ€ÀrS¹SÆI€@Õ ôܺsøØ“ù¿>{^¸÷Ñg«ÖÂÄ hZ÷ä‚Ê‹—,K=â°¡u+ÿëÙ´î Ñ @€ @€X-à_÷V[H @ b†îÓ;ì?pÛd|Ó¦¿¦xßrÅÞ+#@`•@ÜaáÍ·ç'™cÚ)ìÚ· @€ @€š°€Àr¾y†N€@u œ˜[í·SÏ-’I?òïaâäç« Àl h‹—¼n¿ïÉðìK³’ñùÐvá ½z5‰±$ @€ @€kh±2w¬¹Z T’Àü…KÃu}2¼øú»É°zõØ<º_ÿС}ÛJ¦± P¥q…rÜþ:¾[9{÷ïNùè®UªaÚ @€ @€š—€ÀróºŸfC€@•Ü8aj˜üôÉl;ulöÚ¥gØuÇmªdö¦I€@¥ ÔÔ¬Sž~9L~rf:´#öÛ! Û‡4/A€ @€ @€@ÓXnÚ÷Ïè ¨b{™Æ?<=Ør³Ž¹s¯°cŸ­Ò2 lh?ójTŽ[`Ç£EîsÊ‘»†½ôHòþC€ @€ @€@óXn÷Ñ,¨Rgf¼îüR˜þÚª­±#Ãf;„>Ûlzç>Ûuß¼JeL› %°<·:ùå׿†™oÌ /½>7,Z¼,½Ô;nÙ·OØv«Ni™ @€ @€ÍC@`¹yÜG³ @ Êþõì›áïÍ ¯¿½ #±IÛÖ¡ëæ›†ŽíÚ†ŽÚ†¹ï¶mZgÚÈ @ œÀ’¥ï‡EK–……‹rŸÜw|òÊ•+3§ôï½e8dŸ>¡oO¿Ì’‘!@€ @€ ÐŒ–›ÑÍ4üëÙYaÚ‹o‡§_z;Zµ--l-º´»ôélß5ôß~Ë q } @€ @€ PAËt3 … )ðüËsÃ33æ„w, . ór« çç¾—½_Ó—ÑÍ\ ý&­C玛„ÎrŸMÛ†n›uýwèznݹ™ÏÜô @€ @€¨- °\[Cš @€ @€ @€ŠZ•( @€ @€ @€ @€µ–kaH @€ @€ @€ @€@±€Àr±‰ @`=æÍ›·g;• @€ @€*Q@`¹ïŠ1 @€&*pýõׇm¶Ù&œ{î¹á7Þh¢³0l @€ @€(h±2wÊ @€ê#°ß~û…G}4=uÔ¨Q!~úöí›–I @€ @€ @€@Ó°b¹éÝ3#&@€+på•W†OúÓéøFúõëÎ8ãŒðä“O¦å @€ @€ д¬XnZ÷Ëh  @€@“˜6mZ¸üòËÃÕW_ïÈ‘#“̃ΔË @€ @€ @€@e ,Wöý1: ФfΜâªåd®©©IçrÜqÇ%æ¡C‡¦e @€ @€ P¹Ë•{oŒŒ4Ù³g§æ ¤ó6lX`>|xZ&A€ @€ @€•' °\y÷Ĉ @€@³X¸pa`ž5kV:Ï!C†$æ#F¤e @€ @€ P9Ë•s/Œ„TÀÊ•+ÓóŒ3Òy4( 0ŸvÚii™ @€ @€l|匀TµÀ5×\“¼ƒyêÔ©©Cÿþý“óYg•–I @€ @€ @€ÀÆXÞxö®L€Ô3fL`ž=³C‡i€¹Gi¹ @€ @€lXå ë«w @`=&Mš”˜ÇŽ›é)n?}ûöÍ”Ë @€ @€ @€@à ,7¼©  @€ 0eÊ”$À|Ýu×ez?ýôÓ“óÀ3å2 @€ @€ ÐpË g©' @ ¦M›–˜¯¾úêÌÕFŽ™˜œ)—!@€ @€ @€õX^C= @€A`æÌ™aôèÑI¹¦¦&ÁqÇ—˜‡š–I @€ @€ @€Àú ,¯ŸŸ³  @€,0{öì4À¼`Á‚t4Æ KÌÇOË$ @€ @€ @ ~Ëõss Pa .L̳fÍJG7dÈ$Àlذ$À<|øð´L‚ @€ @€[@`ycß×'@€ªZ`áÂ…i€yÖ¬Y©Å!C’óˆ#Ò2  @€ @€l,å%ïº @€Z+W®LÌ3fÌHk ”˜O;í´´L‚ @€ @€- °ÜØâ®G€X‹À5×\â;˜§Nš¶ìß¿`>묳Ò2  @€ @€4–€ÀrcI» @ ŽcÆŒIÌ“'ONÏìÕ«W`5jThÓ¦MZ.A€ @€ @€ ) °¼!uõM€hqãÆ%æ‰'¦½uíÚ5 0wéÒ%-— @€ @€ @€À†XÞªú$@€l &$æñãǧ½wèÐ! 0÷èÑ#-— @€ @€ @€@C ,7¤¦¾ @€ 0iÒ¤$Àû윾  @€ @€6O@°¼yö®L€ pX`zz:Ì===aaa!™ìÝ»7Ìçž{nê) @€ @€ØÁò渻* @€ÀCfggSÀ<77—¾Ý½{w ˜Ï;ï¼ÔS @€ @€ °±‚åõv5 @€G˜ŸŸOóÌÌLÚ{×®]1`¾ð SOA€ @€ @€ÀÆ–7ÆÙU @€V(°¸¸˜æ©©©tôÎ;cÀ|饗¦ž‚ @€ @€õ,¯¯¯³ @€¬@___ÈÞÁ<11‘Îvúé§Ç€ùŠ+®H= @€ @€ë# X^Wg%@€X0:t(}Û¶m1`Þ·o_(++K} @€ @€k' X^;Kg"@€Ø ¡¡¡08p ]ñä“ONsEEEê+ @€ @€8qÁò‰: @€À& ŒŒŒÄ€ù†nH#زeK ˜«ªªR_A€ @€ @€ÀêË«·s$ @€@žŒÅ€ùúë¯ÏQöxì짦¦&§oƒ @€ @€• –Wæeo @€<û÷ïýýý9£¼üòËcÀ¼cÇŽœ¾  @€ @€ŽO@°||Nö"@€( ÉÉÉ0÷ööæŒúâ‹/ŽóÙgŸÓ·A€ @€ @€À±ËÇöñ- @€@ LOOÇ€¹§§',,,¤™ìÝ»7Ìçž{nê) @€ @€8º€`ùè6¾!@€(ÙÙÙ0ÏÍÍ¥YíÞ½;Ìçw^ê) @€ @€x¸€`ùá&: @€E*0??Ÿæ™™™4Ë]»vÅ€ù /L= @€ @€?,ÿÌBE€ P"‹‹‹)`žššJ³Þ¹sg ˜/½ôÒÔS @€ @€ ‚`Ù_ @€@I ôõõ…ìÌÉáôÓOóW\‘z  @€ @€¥, X.åÕ7w @€$000æC‡¥Þ¶mÛbÀ¼oß¾PVV–ú  @€ @€¥& X.µ7_ @€c Å€ùÀi¿“O>9Ì©¯ @€ @€ P*‚åRYió$@€X‘ÀÈÈH ˜o¸á†tÜ–-[RÀ\UU•ú  @€ @€Å. X.ö6? @€‹óõ×_ŸsžìñØÙOMMMNß @€ @€b,㪚 @€Àš Œ‡ýû÷‡þþþœs_~ùå1`Þ±cGNß @€ @€b,Ójš  @€Àº LNNÆ€¹··7çZ_|q ˜Ï>û윾  @€ @€Å X.†U4 @€ ˜žžŽsOOOXXXH×ß»woèêê MMM©§ @€ @€ Pè‚åB_Aã'@€ØTÙÙÙ0ÏÍÍ¥±ìÞ½;Ì---©§ @€ @€ P¨‚åB]9ã&@€È+ùùù0ÏÌ̤±íÚµ+Ì­­­©§ @€ @€ Ph‚åB[1ã%@€ÈkÅÅÅ0OMM¥±îܹ3Ìííí©§ @€ @€ P(‚åBY)ã$@€(8¾¾¾½ƒybb"ýôÓOsgggê) @€ @€什`9ßWÈø @€ ^``` ̇JsÙ¶m[Ø·o_ü)++K} @€ @€|,ç㪠@€@Q Å€ùÀi~'Ÿ|r ˜+**R_A€ @€ @ ŸËù´ÆB€ P###1`¾á†Ò|·lÙ’檪ªÔW @€ @€ÈÁr>¬‚1 @€”¤ÀØØX ˜¯¿þúœù/="»¦¦&§oƒ @€ @€Àf –7KÞu  @€üÀøøxØ¿èïïÏ1¹üòËã]Ì;vìÈéÛ @€ @€ °Ñ‚åw= @€G˜œœŒsoooÎ_|q ˜Ï>û윾  @€ @€% XÞ(i×!@€ pœÓÓÓ1`îéé 騽{÷†®®®ÐÔÔ”z  @€ @€! XÞe× @€ ° ÙÙÙ0ÏÍÍ¥3ìÞ½;Ì---©§ @€ @€ °ž‚åõÔun @€k 0??Ÿæ™™™tÆ]»vÅ€¹µµ5õ @€ @€ÖC@°¼ªÎI€ @`SÀ<55•®°sçÎ0···§ž‚ @€ @€ÀZ –×RÓ¹ @€l@___ÈÞÁ<11‘®xúé§Ç€¹³³3õ @€ @€ÖB@°¼ŠÎA€ @`“bÀ|èС4‚mÛ¶Å€¹»»;”••¥¾‚ @€ @€ÀjË«•s @€<ŠóÒ¨N>ùä0WTT¤¾‚ @€ @€ÀJË+³? @€<‰ó 7ÜF¹eË–0WUU¥¾‚ @€ @€Àñ –WÊ~ @€ H`ll,ìß¿? 挺««+dÈ®©©ÉéÛ @€ @€ p,Áò±t|G€ @ ÀÆÇÇcÀÜßߟ3“Ë/¿<ìÛ·/ìØ±#§oƒ @€ @€Àr‚ååTô @€™Àääd ˜{{{sfvñÅÇ€ùì³ÏÎéÛ @€ @€ p¤€`ùH 5 @€"˜žžŽsOOOXXXH³Ý»wo|sSSSê) @€ @€, –—$ü&@€ PB³³³)`ž››K3ß½{w ˜[ZZRO±6÷Ýw_Èî¿å–[·¿ýíð´§=-lß¾=¾ïzëÖ­áQzÔÚ\ÈY @€ @€ë X^T§$@€ P(óóó)`ž™™IÃÞµkW ˜[[[SO±:›nº)¼á o·ÞzëQOpÎ9ç„¿þë¿gœqÆQ÷9‘/–Bëç<ç9áöÛoO§úõ_ÿõðñó™pÑE…3Ï<3\{íµ¥…²F³}ðÁÃ;ÞñŽðÒ—¾ô˜¡rv¹ÑÑÑð‹¿ø‹áÊ+¯\£«/šx ç‹#·|4zÎN6 @€ @€‡ܱìÏ€ @€¾¾¾½ƒybb"õO?ýôxsgggê)Ž.ðÓŸþ4¼â¯ŸøÄ'ÒNO}êSCvøóŸÿüøøëìñì.櫯¾:ù8ò÷¿ÿý¡½½=·ÅÒ˧vZ¸ãŽ;Ò)_þò—‡n¸!nßu×]á”SNIß) @€ @€G –ÔP @€ bÀ|èСÔÛ¶m[ ˜»»»CYYYê+rÞóž÷„ßùßIÍóÏ??üýßÿ}8ùä“So©ø¯ÿú¯pÉ%—ä„ÐY¨ÿ¼ç=oi—þ-X>aB' @€ @€%/àQØ%ÿ'€ @€Àò¯zÕ«ÂÁƒÃG>ò‘ð’—¼$îôo|#¼éMo [·n W]uUøÁ~°üÁ%ÜÍL²Ç‹/}^ûÚ׆}ìcˆÊÙ>O~ò“Ã?øÁ½ÿxéÓßß¿TúM€ @€È w,çÅ2 @€ü‰w0/=:9ñ–-[ÒÌUUUù?‰ áŸüÉŸ„·¿ýíéJÙû«Ÿþô§§í£Y˜|饗Ư³ùöÛoØ®÷Ýw_¸þúëÿøÅðõ¯=dïqÎ_½;{ôvö¨ëå>«½cùþûïÿøÿ¯÷Õ¯~5ž:{ôgœ~õW5<å)OYîrz @€ @€@ –‹pQM‰ @€Àz Œ…ýû÷‡ÁÁÁœËtuu…ìÙ5559ýRÛÈæŸ…¾ÙçþèrBæcYüèG? ïz×»â.~ô£ã±Ùï¥Ï¿ýÛ¿…ßøßÈyóÒwK¿ë·~+üÝßýÝÃS¾š`9»[½­­-ÍeéK¿³wFg!÷®]»–Z~ @€ @€E, X.âÅ55 @€ë)0>>æ‡>¶¹££#Ì;vìXÏËçå¹ÿû¿ÿ;<þñOc»å–[B]]]Ú^mñío;>*{nn.⬳Ί"ÿÊW¾’zY‘½ÛùꫯÎé­4XþÎw¾²õË~/}²w>?á Oˆw//õ²ßŸùÌgBccã‘-5 @€ P„?ûçïE89S"@€ @`ýÎ<óÌðÿðáË_þrxÝë^—.”Ý1›…©_|qøüç?Ÿú¥PLOOçL3{LõZ|~ï÷~/Ý©œÝ•œÌÙÅÙ㲿ûÝï†÷¼ç=é2Ùš,..¦íÕï{ßûR¨|þùç‡ÙÙÙ011²LðÃþ0g½ßö¶·­æŽ!@€ @€ L@°\` f¸ @€òMà¹Ï}n¸æškÂwÞÞøÆ7†Ç<æ1qˆ×]w]hhh{÷î 7ÝtS¾ {]ÆsÇwäœ÷ÉO~rÎöj7>ýéO§C{zz“žô¤´]YY:;;Ó]ÃYèü­o}+}¿šâ _øB:ì÷ÿ÷ÃÉ'Ÿœ¶îç~.üå_þeÈ~gŸo¼ñ„ƒìtr @€ ·‚å¼]#@€ PXÏ|æ3càxÏ=÷Ä÷/ûØÇÂK_úÒ°{÷îð/ÿò/…5©ŽöÞ{ïMG<ç9ÏIõ‰Ùã­¯¼òÊÐ××~á~áa§{èÊ÷ßÿÃöYIãÇ?þqÚ={—öƒ>˜¶³bË–-ñØYà}d賓  @€ @ ¨¼c¹¨–Ód @€äÀüü||sv‡íÌÌLXö>Þ}ûö…ÖÖÖÔ+–âŸøDhii‰ÓyêSŸš3ﵜcö.çì®ä»îº+|ãßïÿûsî ÿú׿N=õÔtÉ•¾cùOÿôOÃÿñ§ãO;í´pÙe—…_ù•_ ;wî {ÜãÒw  @€ @ 4Ë¥±ÎfI€ @`Ó²»i÷ïß²€yjj*# (»ººB{{{êzñ¹Ï}.=’:›Ë}÷Ýžð„'¬É´²÷¿ë]ï ýèGÃW¾ò•cžóDƒåìqÚ¿ök¿>ûÙÏ.{ .¸ dï^¾ð s˽ìΚ @€ @€@QxvQ,£I @€È_ìnÙîîî…ï{ßûÂgœûÅ/~1Þ›½£ù½ï}oþN`#;å”SröÎî(>ÞÏO~ò“PSS~þç>þ|õ«_M‡^{íµ!»úÏÿüÏ— •_üâ§w§ƒN Èc>22ÞùÎw†‡Î);íÐÐPxõ«_¶nÝšs§ô \Ò¡ @€ @€@ž –ó|  @€@1 ¼æ5¯ ·Ýv[øÀ>Î:ë¬8µÿüÏÿ ¯ýëCöŽæìŽÜ}?ðfzmÛ¶-'ˆýð‡?|ÜÃÉÞ?…ïÙÝÂÙgéQÖ7ß|s à—N”½»ùÿðÃððpÈì~úÓŸÆ÷755-í–}«(žøÄ'†?øƒ?ˆÛž˜˜ˆáv‡òÒ»³³SfcÍÞŸ}÷Ýw¯â !@€ @€ I@°\H«e¬ @€ŠDàU¯zU8xð`øÈG>^ò’—ÄYeï ~Ó›ÞꪫÂ~ðƒ‚œíÒ;–³ÁÿÙŸýYøáx\óøÛ¿ýÛ´_ö.ãÇ>ö±qûCúPêg§¾õÖ[ãy³º¶¶6½ïøÈ;œÓ«(²G—gqö“…ÖÙçyÏ{^¸âŠ+B6–{ï½7|üãÏ ˜öÈìU\Þ! @€ @€@ž –óta ‹ @€@)dïê Ÿüä'ã;}³9÷»ß o}ë[cÀüæ7¿9ÜsÏ=EÑÙٙƛ…³o{ÛÛBÖëóïÿþïñÑÓKû´¶¶.•!»cyésÙe—…Ç?þñK›é÷·¿ýíðå/9m?ÒõÒŽË_ûÚ×Òã¸ÛÚÚ¶GYYYÈÂó¿ø‹¿HßÝqÇ©V @€ @€Å) X.Îu5+ @€%ð²—½,>ÚùÓŸþtxå+_Çþãÿ8¾ã7{ï¾}ûB¡„—;vì_|qòÿ«¿ú«pÑE…ýèG©wd‘=.;»Cyé“݉œí¿ô©¬¬\*—½‹;;ïk_ûÚ´OVœÈ£°—Á'{—òÑîÏî0_údçö!@€ @€Š[@°\Üëkv @€ JàÅ/~qÈ‚Ö/|á á’K.Ic¿úê«ÃöíÛÃå—_þã?þ#õóµxï{ß½4¾lNÏ}îsCGGGxÏ{Þ®¿þúxWvö~â#ïNÎÞ_ü7ó79Áð ^ð‚¥Ó„7¾ññ]ÇY¨›=;Û·±±1>š:ít¸Èü޹ßruöî#ÇôË¿üËásŸû\È‚þx ž;{Tù;ÞñŽtxCCCª @€ @€@q <êð#ÒŽýL¶âœ·Y @€ P“““aÿþý¡··7g´Ù#š³»˜ó9Ðüæ7¿^ýêW‡o¼1gìGÛÈâþþþpÚi§åì’=N;{Çqv¾£}N9å”x×óµ×^›vÙ¹sgÛKw0gç>òÎï—¿üåá†nˆûÜu×]!;OöÉ®U]]ëGúŸk®¹&¼îu¯{¤Ý|O€ @€¸€;– | Ÿ @€@1 dwùfÁåwÞïÖ}Ìc§{Ýu×…½èEaï޽ᦛnÊK‚,¤ý×ý×088žúÔ§uŒÙ]ÊÙûŠ8ð°P9;(û>;ÏYgµì9.¼ðÂøæw¾óËäAÙÝÈG~ŽÜ^²Í¾ÏÆ~Ë-·Ä»¡ÜÿÈ:›S„ÿöoÿö‘m5 @€ P¤îX.Ò…5- @€Å(0;;ï`îéé Ù¼KŸæææxsKKËR+¯~/,,„ìñÕ·ß~{üyðÁÃgœïD~Æ3ž‘óèë£ <;Çç?ÿùÝÅý“Ÿü$¼ð…/ ¿ôK¿÷¸Ç¥C²÷-ß|óÍá¾ûî §Ÿ~zضm[ún5E6Îì±ÚSSSñ'{ö3Ÿù̽‡9»þ“žô¤ÕœÖ1 @€ @€@ – pÑ ™ @€@© ÌÏϧ€yff&qd“Α}ä;‚Ó—  @€ @€U –WMç@ @€6[`qq1ÌÙ]µKŸúúú0···/µü&@€ @€8Áò à9” @€üèëë Ù#²'&&Ò jkkcÀÜÙÙ™z  @€ @€• –Wnæ @€òX``` ̇J£¬®®Žswww(++K} @€ @€Àñ –ÏÉ^ @€˜ÀÐÐP ˜8F^YY™把ŠÔW @€ @€[@°|lß @€ Pà###ñ=ÌÃÃÃi&ååå)`®ªªJ} @€ @€Àò‚åå]t  @€(2±±±0æÌ¬««+dÈ®©©ÉéÛ @€ @€ø™€`ùg* @€J@`||<Ìýýý9³íèèˆóŽ;rú6 @€ @€B,û+ @€ @ $&''cÀÜÛÛ›3ÿ¶¶¶ø˜ì†††œ¾  @€ @€@) –KyõÍ @€0==æžžž°°°DöìÙ榦¦ÔS @€ @€JU@°\ª+oÞ @€äÌÎΦ€ynn.}×ÜÜæ–––ÔS @€ @€JM@°\j+n¾ @€S`~~>Ì333ißÆÆÆ0·¶¶¦ž‚ @€ P*‚åRYió$@€ @`E‹‹‹)`žššJÇÖ××Ç€¹½½=õ @€ @€b,û › @€',Ð×ײw0OLL¤sÕÖÖÆ€¹³³3õ @€ @€b,ëÊš @€k.000æC‡¥sWWWÇ€¹»»;”••¥¾‚ @€ PL‚åbZMs!@€ @`C†††bÀ|àÀt½ÊÊÊ0WTT¤¾‚ @€ P ‚åbXEs @€ @`SFFFâ{˜‡‡‡ÓõËËËSÀ\UU•ú  @€ @€@! – yõŒ @€¼‹óàà`ÎxºººBöˆìšššœ¾  @€ @€@¡ – mÅŒ— @€¼sÎ;::bÀ¼cÇŽœ¾  @€ @€@¡– e¥Œ“ @€‚˜œœŒsoooΘÛÚÚâc²rú6 @€ @€ù. XÎ÷2> @€ V`zz:Ì===aaa!ÍcÏž=1`njjJ= @€ @ ŸËù¼:ÆF€ @€@QÌÎΦ€ynn.Í©¹¹9Ì---©§ @€ @€䣀`9WŘ @€(Jùùù0ÏÌ̤9666Æ€¹µµ5õ @€ @€|,çÓj  @€%!°¸¸˜æ©©©4çúúú0···§ž‚ @€ ‚å|Xc @€ @ dúúúBö扉‰dP[[æÎÎÎÔS @€ @€6S@°¼™ú®M€ @€ÿˆó¡C‡’Iuuu ˜»»»CYYYê+ @€ @€- XÞhq×#@€ @€À1†††bÀ|àÀ´Weee ˜+**R_A€ @€Ø(ÁòFI» @€V 022ßÃ<<<œŽ*//OsUUUê+ @€ @€ë- X^oaç'@€ @€À ŒÅ€ypp0ç,]]]!{DvMMMNß @€ @`=Ëë¡êœ @€Xcñññ0÷÷÷眹££#Ì;vìÈéÛ @€ @€¬¥€`y-5‹ @€ë,099æÞÞÞœ+µµµÅÇd744äôm @€ @€ÖB@°¼ŠÎA€ @€ ˜žžŽsOOOXXXHWß³gO ˜›ššROA€ @€8QÁò‰ :ž @€›(0;;›æ¹¹¹4’æææ0·´´¤ž‚ @€ °ZÁòjåG€ @€<˜ŸŸOóÌÌLYccc ˜[[[SOA€ @€X©€`y¥bö'@€ @€@ ,..¦€yjj*´¾¾>Ìííí©§ @€ @€¯€`ùx¥ìG€ @€èëë Ù;˜'&&ÒÈkkkcÀÜÙÙ™z  @€ @€À# –IÈ÷ @€(p0:t(ͤºº:ÌÝÝÝ¡¬¬,õ @€ @€åËË©è @€ @ †††âc²GGGÓì*++SÀ\QQ‘ú  @€ @€À‘‚å#5Ô @€(‘‘‘0§Ù–——§€¹ªª*õ @€ @€L@°ìï€ @€%*066æÁÁÁ®®®="»¦¦&§oƒ @€(]Ár鮽™ @€ @ ŒÇ€¹¿¿?G¤££#ÞÅ\WW—Ó·A€ @€”ž€`¹ôÖÜŒ  @€ °¬Àääd ˜{{{s¾okk‹sCCCNß @€ P:‚åÒYk3%@€ @€Àq LOOÇ€¹§§',,,¤cöìÙ榦¦ÔS @€ @€¥! X.u6K @€¬X`vv6Ìssséøæææ0·´´¤ž‚ @€(nÁrq¯¯Ù @€ @à„æççSÀ<33“ÎרØæÖÖÖÔS @€ @€Å) X.Îu5+ @€¬¹Àââb ˜§¦¦ÒùëëëcÀÜÞÜô¼6@IDATÞžz  @€ @ ¸ËŵžfC€ @€ èëë Ù;˜'&&ÒõjkkcÀÜÙÙ™z  @€ @ 8ËűŽfA€ @€Mˆó¡C‡Òõ«««cÀÜÝÝÊÊÊR_A€ @€®€`¹p×ÎÈ  @€ 7CCCñ1Ù£££iL•••)`®¨¨H} @€ Px‚åÂ[3#&@€ @€@Þ ŒŒŒÄ€yxx8±¼¼<ÌUUU©¯ @€ @€ G@°\8ke¤ @€(±±±0挹««+dÈ®©©ÉéÛ @€ @€ò[@°œßëct @€(hñññ0÷÷÷çÌ£££#ÞÅ\WW—Ó·A€ @€ä§€`9?×Ũ @€ PT“““1`îííÍ™W[[[ ˜rú6 @€ @€ü,ç×z  @€ŠZ`zz:Ì===aaa!ÍuÏž=1`njjJ= @€ ?‚åüY #!@€ @€@ÉÌÎΦ€ynn.Í»¹¹9Ì---©§ @€ @€6_@°¼ùk` @€(Yùùù0ÏÌÌ$‡ÆÆÆ0·¶¶¦ž‚ @€Ø<ÁòæÙ»2 @€üÀââb ˜§¦¦’K}}} ˜ÛÛÛSOA€ @€l¼€`yãÍ]‘ @€Ž!Ð×ײw0OLL¤½jkkcÀÜÙÙ™z  @€ @`ãËgíJ @€ °0:t(U]]æîîîPVV–ú  @€ @`}Ëëëëì @€ p‚CCCñ1Ù£££éL•••)`®¨¨H} @€ °>‚åõquV @€Xc‘‘‘0§3———§€¹ªª*õ @€ @€ÀÚ –×ÖÓÙ @€ @`ÆÆÆbÀ<88˜s¥®®®="»¦¦&§oƒ @€8qÁò‰: @€l‚Àøøx ˜ûûûs®ÞÑÑïb®««ËéÛ @€ @€V/ X^½#  @€ @ &''cÀÜÛÛ›3š¶¶¶0744äôm @€ @€+,¯ÜÌ @€ ‡ÓÓÓ1`îéé i„{öì‰sSSSê) @€ @€• –Wæeo @€ÈsÙÙÙ0ÏÍÍ¥Ñ677Ç€¹¥¥%õ @€ @€Àñ –ÏÉ^ @€ P`óóó)`ž™™I£ollŒskkkê) @€ @€c –íã[ @€(pÅÅÅ0OMM¥ÙÔ××Ç€¹½½=õ @€ @€Àò‚åå]t  @€ @ úúúBö扉‰4»ÚÚÚ0wvv¦ž‚ @€È,çzØ"@€ @€ˆó¡C‡Òl«««cÀÜÝÝÊÊÊR_A€ @€„ XöW@€ @€%+044“=::š *++SÀ\QQ‘ú  @€ PÊ‚åR^}s'@€ @€(022æááá$R^^ž檪ªÔW @€ @€R,—⪛3 @€,+066æÁÁÁœï»ººBöˆìšššœ¾  @€ P*‚åRYió$@€ @€ãsÎ1ñ.溺ºœ¾  @€ Pì‚åb_aó#@€ @€U LNNÆ€¹··7çmmm1`nhhÈéÛ @€ @€Å* X.Ö•5/ @€X3ééé0÷ôô„………tÞ={öÄ€¹©©)õ @€ @ ËŸªæD€ @€ë"0;;›æ¹¹¹tæææ0·´´¤ž‚ @€“€`¹˜VÓ\ @€ @`CæççSÀ<33“®ÙØØæÖÖÖÔS @€ @€b,Ã*š @€lŠÀââb ˜§¦¦ÒêëëcÀÜÞÞžz  @€ PÈ‚åB^=c'@€ @€¼èëë Ù;˜'&&Ò˜jkkcÀÜÙÙ™z  @€ Pˆ‚åB\5c&@€ @€¼ˆw1^)û @€ @€˜œœŒsoooΈÛÚÚbÀÜÐÐÓ·A€ @€I@°üHB¾'@€ @€*0==æžžž°°°f±gÏž0755¥ž‚ @€K@°|,ß @€ @€"˜MóÜÜ\šQsss ˜[ZZROA€ @€–,/§¢G€ @€ŠP`~~>Ì333i†1`nmmM= @€8R@°|¤†š @€”€Àââb ˜§¦¦ÒŒëëëcÀÜÞÞžz  @€ –ý @€ @€èëë Ù;˜'&&’Bmmm ˜;;;SOA€ @€¥- X.íõ7{ @€ â]ÌL"ÕÕÕ1`îîîeee©¯ @€ @€Ò,—Þš›1 @€8ªÀÐÐP ˜GGGÓ>•••)`®¨¨H} @€”Ž€`¹tÖÚL  @€ @€Àq ŒŒŒÄ€yxx8S^^ž檪ªÔW @€ @€@ñ –‹Í @€¬Z`ll,̃ƒƒ9çèêêŠ!óöíÛsú6 @€ @ 8ËŹ®fE€ @€ÖT`||<Ìýýý9çíèèˆs]]]Nß @€—€`¹¸ÖÓl @€ @€Àº LNNÆ€¹··7ç:mmm1`nhhÈéÛ @€ @€â,Ç:š @€ØPééé0÷ôô„………tí={öÄ€¹©©)õ @€ Pø‚åÂ_C3 @€ @€›&0;;›æ¹¹¹4Žæææ0·´´¤ž‚ @€ W@°\¸kgä @€ @ oæççSÀ<33“ÆÕØØæÖÖÖÔS @€ @€@á – oÍŒ˜ @€ä­Àââb ˜§¦¦Ò8ëëëcÀÜÞÞžz  @€(Árᬕ‘ @€ @€‚èëë‹!óm·Ý–Æ][[æÎÎÎÔS @€ @€@þ –óŒ @€´ÀÀÀ@ ˜<˜æQ]]æîîîPVV–ú  @€ÈOÁr~®‹Q @€ @€¢Šóèèhš[eee ˜+**R_A€ @€ù% Xίõ0 @€ Pô###1`Ns-//OsUUUê+ @€ @ ?Ëù±FA€ @€JN`ll,̃ƒƒ9sïêêŠ!óöíÛsú6 @€ @`óË›gïÊ @€ @€Àañññ0÷÷÷çxtttÄ€¹®®.§oƒ @€6^@°¼ñæ®H€ @€,#099æÞÞÞœoÛÚÚbÀÜÐÐÓ·A€ @€' XÞ8kW"@€ @€ŽC`zz:Ì===aaa!±gÏž0755¥ž‚ @€6F@°¼1ήB€ @€¬P`vv6Ìssséèæææ0·´´¤ž‚ @€ÖW@°¼¾¾ÎN€ @€œ Àüü| ˜gffÒÙcÀÜÚÚšz  @€XÁòú¸:+ @€ °Æ‹‹‹)`žššJg¯¯¯s{{{ê) @€ @`mËkëél @€ @€ÀôõõÅù¶ÛnKW«­­sgggê) @€ @`mËkãè, @€ @€À& Ä€ùàÁƒéêÕÕÕ1`îîîeee©¯ @€ @€Õ –WoçH @€ @ O†††bÀ<::šFTYY™把ŠÔW @€ @€ÀÊË+7s @€ §###1`N#,//ó¾}ûÂÖ­[S_A€ @€Ç/ X>~+{ @€ @€"066æÁÁÁœwuuÅyûöí9} @€ plÁò±}|K€ @€°Àøøx ˜ûûûsfÑÑÑ溺ºœ¾  @€X^@°¼¼‹. @€ PD“““1`îííÍ™U[[[ ˜rú6 @€ @ W@°œëa‹ @€(bééé0÷ôô„………4Ó={öÄ€¹©©)õ @€ ð3ÁòÏ,T @€ @€@‰ÌÎΦ€ynn.ͺ¹¹9Ì---©§ @€ @€Ëþ  @€ @€’˜ŸŸOóÌÌLrhllŒskkkê) @€ PÊ‚åR^}s'@€ @€¢Àââb ˜§¦¦’J}}} ˜ÛÛÛSOA€ @€R,—⪛3 @€ pT¾¾¾2ßvÛmiŸÚÚÚ0wvv¦ž‚ @€¥$ X.¥Õ6W @€ @à¸bÀ|ðàÁtLuuu ˜»»»CYYYê+ @€ Pì‚åb_aó#@€ @€NH`hh(Ì£££é<•••)`®¨¨H} @€ŠU@°\¬+k^ @€ @€Àš ŒŒŒÄ€yxx8·¼¼<Ìûöí [·nM} @€ŠM@°\l+j> @€ @€Àº ŒÅ€ypp0ç:]]]1dÞ¾}{Nß @€ŠA@°\ «h @€ @€À† ŒÇ€¹¿¿?çÚ1`®««ËéÛ @€ @€@! – yõŒ @€ØtÉÉÉ0÷öö挥­­-Ì 9} @€(DÁr!®š1 @€ @€y'0==æžžž°°°Æ·gÏž0755¥ž‚ @€…& X.´3^ @€ @ ¯fggSÀ<77—ÆÚÜÜæ–––ÔS @€ @ PË…²RÆI€ @€”Àüü| ˜gffÒØcÀÜÚÚšz  @€什`9ßWÈø @€ @€‚X\\LóÔÔTšK}}} ˜ÛÛÛSOA€ @€|,çëÊ @€ Pt}}}1d¾í¶ÛÒÜjkkcÀÜÙÙ™z  @€䛀`9ßVÄx @€ @€¢ˆóÁƒÓ\«««cÀÜÝÝÊÊÊR_A€ @€|,çÃ* @€ P’CCC1`M󯬬LsEEEê+ @€ °™‚åÍÔwm @€ @€Àa‘‘‘0'òòò0ïÛ·/lݺ5õ @€Ø Áòf¨»& @€ @`±±±0æ|ÛÕÕCæíÛ·çôm @€ @`£Ë%í: @€ @€ãsÎ1`®««ËéÛ @€ @€Àz –×[Øù  @€ @€«˜œœŒsoooÎÚÚÚbÀÜÐÐÓ·A€ @€õ,¯—¬ó @€ @€ÖH`zz:Ì===aaa!uÏž=1`njjJ= @€ÖC@°¼ªÎI€ @€XÙÙÙ0ÏÍÍ¥+477Ç€¹¥¥%õ @€XKÁòZj: @€ @`æççSÀ<33“®ØØØæÖÖÖÔS @€ @`-Ëk¡è @€ @€MX\\LóÔÔTA}}} ˜ÛÛÛSOA€ @€,Ÿˆžc  @€ @€y"Ð×ײw0OLL¤ÕÖÖÆ€¹³³3õ @€X€`y5jŽ!@€ @€ä©ÀÀÀ@¼‹ùàÁƒi„ÕÕÕ1`îîîeee©¯ @€ @€Àñ –WÊ~ @€ @€Šóèèhueee ˜+**R_A€ @€G,?’ï  @€ @€,022æááá4‹òòò0ïÛ·/lݺ5õ @€8š€`ùh2ú @€ @€"‹óàà`άºººbȼ}ûöœ¾  @€) X>RCM€ @€(rñññ0÷÷÷çÌ´££#Ìuuu9} @€ÈËþ @€ @€%(099æÞÞޜٷµµÅ€¹¡¡!§oƒ @€Ò,—öú›= @€ PâÓÓÓ1`îéé IcÏž=1`njjJ= @€¥+ X.ݵ7s @€ @€@˜MóÜÜ\ê777Ç€¹¥¥%õ @€”ž€`¹ôÖÜŒ  @€ @€G˜ŸŸOóÌÌLÚ¯±±1Ì­­­©§ @€ @ tË¥³ÖfJ€ @€8nÅÅÅ0OMM¥ãêëëcÀÜÞÞžz  @€Š_@°\ükl† @€ @€èëë Ù;˜'&&ÒyjkkcÀÜÙÙ™z  @€ŠW@°\¼kkf @€ @€5ˆó¡C‡Òy«««cÀÜÝÝÊÊÊR_A€ @€@q –‹k=͆ @€ °îCCCñ1Ù£££éZ•••1`Þ·o_8餓R_A€ @€@q–‹cÍ‚ @€ °á###1`N×.//OóÖ­[S_A€ @€@a – {ýŒž @€ °éccc1`ÌKWWW ™·oßžÓ·A€ @€@á – oÍŒ˜ @€ —ããã1`îïïÏ_GGG ˜ëêêrú6 @€(Árᬕ‘ @€ @€ B`rr2̽½½9ãmkk‹sCCCN-6¾ô¥/…ç?ÿùkq*ç @€ @`G/ÓÓ"@€ @€ °jç>÷¹ášk® wÞygxãßó˜ÇÄs]wÝuáE/zQØ»wo¸é¦›V}þå¼òÊ+C.û @€ °>îX^Wg%@€ @€øÙÙÙxsOOO˜››K.ÍÍÍñæ–––Ô[mqÎ9ç„ééépóÍ7‡“N:iµ§q @€GpÇòQ`´  @€ @€ÖFà)OyJxûÛßî¹çžðŽw¼#<íiO‹'¾ñÆÃùçŸvíÚ>ô¡ðŲ;¤/»ì²> @€ ðpÁòÃMt @€ @€ÖAà‰O|bxË[Þæw¿ûÝáÔSOWùìg?.ºè¢°sçÎpíµ×žÐ•?úÑ†ì±Ø> @€¬­€Ga¯­§³ @€ @€¬@ ¯¯/dÈž˜˜HGÕÖÖÆGdwvv¦Þ#Ù£°8vûÔ§>²ž @€k#àŽåµqt @€ @€U¼æ5¯ ·Ýv[øÀ>Î:ë¬x†Ûo¿=¼þõ¯Û¶m ïz×»Âý÷ß¿â3_pÁ!{4¶ @€k# X^Gg!@€ @€8W½êUáàÁƒá#ùHxÉK^Ït×]w…7½éM¡ªª*\uÕUáûßÿþq_!Û7 —} @€XÁòÚ8:  @€ @€Àdaðèèhøä'?^þò—Ç3Þ{ï½á­o}kغukxó›ßî¾ûîãºÒ—¾ô¥pÙe—×¾v"@€ @àØ‚åcûø– @€ @`^ö²—…O|âáÓŸþtxå+_Gpß}÷…w¾óáÏxF|ó×¾öµGÙµ×^² @€NL@°|b~Ž&@€ @€XG¿øÅáÃþpøÂ¾.¹ä’t¥«¯¾:<ûÙÏ—_~y¸õÖ[S¹â oxCÈî^ö!@€ @`õZ<üYýáŽ$@€ @€ °q“““aÿþý¡··7碵µµáöÛoÏé¹ñ¬g=+Ü|óÍᤓN:²­&@€ @à8ËÇ e7 @€ @€ü˜žžŽsOOOXXX8®íÝ»7 ×¾v"@€ @ W@°œëa‹ @€ @ €²÷,·´´ónå#§óÖ·¾5\yå•G¶Ô @€‡€`ù8ìB€ @€ Ù{“/»ì²¿?ùSŸúT8çœsòoBFD€ @ Çc34 @€ @€Ë \{íµáÜsÏ]q¨œì‚ .wÞyç²çÕ$@€ @`yÁòò.º @€ @€y*ð»¿û»ñNåïÿû«av\.¯öøU]ÔA @€ \À£° | Ÿ @€ P*Y¼Ú»”—3jooïÿû—ûJ @€‡–b“ @€ @ ÿFGG×å.ãßû_vŸ÷Šü›° @€À† ì~Ñiv-"@€@! <¶oì @€ @€Å/ðîw¿;¼á oX“‰VTT„ç?ÿùé\ÿ<ôá'[ž_þ¤ÔS @€@鼬A¨\:«m¦œ¨€`ùDO€ @€ °nÙ㯳 øSŸúÔîqÎ9ç<¬·ÒÆ5_ûæ÷Vz˜ý  @€”œ€`¹ä–Ü„  @€ @€…#pÒI'…µiÆgíxfxáŽg=Òn¾'@€"ú÷[ÂÝ3ß/’Ù˜6FàÑsW!@€ @€ @€ @€B,êÊ7 @€ @€ @€6H@°¼AÐ.C€ @€ @€ @€B,êÊ7 @€ @€ @€6H@°¼AÐ.C€ @€ @€ @€B,êÊ7 @€ @€ @€6Hà±t—!@€\óáñìmW(6šS~!ì~ÑiÅ6-ó!@€ @€(`Ár/ž¡ PÜ_ûæ÷Š{‚fG€GÈ‚e @€ @€ù$ XΧÕ0±üº @€òL@°œg b8 @€ @€…!°ã¹§Ð@ÿcòë't¼ƒ  @€l¤À£7òb®E€ @€ @€ @€…' X.¼53b @€ @€<xÔ#Œí‘¾„Ã}M€ @`SË›Âî¢ @€ @€-°¸øáÿl;V+þþ!§³I€ @ ϼc9ÏÄp @€ @€ò_àÁÃÁñ,Ä>êð-ÈYŽüÐßK³xhiûÁ\ÚÅo @€y/ XÎû%2@ @€ @€|xðÁ…ÃÁò‡‡•ÝŸœ=Üzå¿Ëù¶ªÆC€ p,Áò±t|G€ @€ @`²;–ãããË|ûÈ­3Ï<3<¸àŽåG–² @€@¾–óe%Œƒ' 04øO'pôѽà•ýKß @€ @ „ß±|ÿá`ùƒü`ø§úÇI\ô›¿êê~)dçð!°3÷ÜþsòÖpÇWoÙðÏ:u{Øö¬ÓBõ3O åå[ÖâÎA€‚e @€ @€V(…w ‡ß±¼¸¸ò»Ž<|·ó‡ÀìñÙ>+èzM[þøõ+9$îûÄ'ý\¸õk÷®ø¸|>àÇ?žvÕ›ÃG?æ4wÔ¡þñÛÿ2ü¿í¯ -+;ê>«ýâo¯ù?á¿íãáÿÁ/iúÕtªš§?.ÖuÏAúäçRÿ?¿üἦ3ÓöJ‹OøRxvíóVz˜ý  @€5,¯¢S @ _Î>óŒ5ÊçÇ'Öä6ü|Å/ïîö#@€ÖX@°¼Æ NG€Íˆ¡qö/†µüÿG㸾ßìI¸> @€ò\ {vv×ñR~¼x¸zÔáÿ[Évv׳ÏêÞñ—½á¢W½zõ'(À#ï»ïÇá÷»:rF~ußuáåç¿"§—m´]òšðoŸüxøíöÿûÝÔ_ WýÑßýŸ÷=lßÍjœZóìðoŸõÔ´Íòw] °RG¯ôû @€@ä„ÊÙx ™³Í‡l?ôû˜¢! @€ @`3b°üÀ‡ß³ü@x þ^øŸßǿÇÀJúÿî=áÞï~'rãgo[6T^ÚáW~õüð¿ÿêo–6ǯ»6üàßKÛ  @€ÀJܱ¼-û @€ @€8,°½cùp œ>K·*/5Žc;;‡ÏÆ Ütã'Âðƒ=Vù¼=þ·×ýÇ×!|gæžð¹±OÅÁ¶ý9¡îù/HýÜØM‡¿ÿvxÜãCÝŸüä¾0~ðsá–›…ÉÛn OßzJøÅºág7†gœ²-·\16zc¸å‹í_úBøÖ7¿NÞŽpÚöÚðÊßlO{zUÎ!ßû¯ï†?ÿÓÿ•z¿ý;o:®w&¿â¢KBÏ_\¾}ÏÿÏÞ]€KQµén¤»é”PF%DETBÊ@DE¤áé.EBBšK_II éo߃3ÌîÝÛ—ËÞÝÿyžqÏœ9S¿åƒoö÷œ¿Ì¾kW¯'ë6´cUöîÞ! çÎ’C÷Ë©“Ç%]ú ®kÈ&¥ËV”5kKÂD‰¬®>ñ¹xÁ/rñŸ æZ2fÊ,UªÕôz] çþÏ5ú5×÷HräÌ-E‹—”Ë—.Êï¿­4ýsåÉownß*³gN–û÷H ¥D©²R¾R5y¸T9¯Ç;{º\»zU¬sŸ8~L¦ü8VvíüS*¸ökóBg¯ûш €@\ °—¿=®ðзÝoºæø²FÂŽÊçmÞ˜÷Pe@@)`ž¿\ó$[ñã¨|òüÒ56Z¾×[tXh-Õª+É“?â´öï•]Úšö^éæX: ¯ ?"¥”)WY^q 5­aoeúÜÕR²tù›4 úv¯Nò‹kdgÙµãO³:bp?yçƒáÒö¥WíÍkV.µëZy±c7·õÐV$H  ùÜuM—)SºuÕ¬û®¯´’ù¿Ìtkw®¤ÏIFl‚åÎöûY_³b±|?n”} ³­3Ac»ÁUÑ rÇöÍì¦q6uu~ñ¹;Áõ®=ß1â1#‡Øý´¢c-/uê!=Þìïš»:±Y·þóêKÏšªÎo?~|×ãMåÒÅL[²dÉ­n|"€ àW–ýêëäf@ Ðnݺéz WߘÊOú†öm¹I`9Ðÿqÿ € € }©×ùüeͱl=EdŒå@ûp ">ýx9·¡©=/·Í3OÉœ¥M¦¬µM³eÖ®d·­voØ xkÛïô| KÕO˜.‡Y]¥v½F®ŒâŒözx•Çž¨+ºx+£>ìTÎ’5›due]oÚø»Ý]‡ßnÞàQY¶v—äÌ×n¿Ÿ•^}Ȳ%óäÈ¡æ2ú¼þ²Ìpò­ÌêsgOKÏ®ííKlÓ¾“T¬¶½nU>öU5Ÿú€ Ö†¯G —#‡÷ËßLvëg­l\ÿ›<×ôIk•O@üZ€À²_½ÜšÀ­›·Íœ^7Þy 9²÷_¦L¹ÅPl‘e£? € € Àí[·åøßç¬8rÔ?Ð.¦nùäñ£¢Ã7‡WÒ¤Mç®8KxÝ¢´]®šÍ«ó?úøS®g꛲fÕiß²¾9ž(çÿ2Ýd½Z'÷å§näo&Ì’ÊU“$I’º†è>'}]™Ìs~žfºønOY°r‹©kµU²çÈmU£õ´g§|â&[‹TÇ|;Õ\‹®k&ó^×öÞ¯½(ÛÿܤM²Ú•%Ü2wS‰ÿ\¹|9Bß¡u®ì9s‹• ¬™æú^šÖ­j6ë5~3æcyåÕÞf]í¬±í{¿3Ð:Œ×Ïî½ûI“Ï› úÑ¿ŽÈ”‰ßÊÈáš¾šÍ½vÍr©X¥Fˆ}­s¨_‡N¯»†3/!ù  Ñ@üA€À²?|‹Ü ðŸÀMWÆò ׃ßĉeòäI‘riÑ¢…”xøaѬg  € € €@ØGO¹‚Ê”û* Q+(Ö…h ±×Ûw„aõ‹Ê6 */X¹YÒ¦Ë`v×!‘5+VϧCfkÙ¶õNPVë:wñÇCúkÕ”‘_ÿä H×±V%Uê4òÞ OíÀ²~ÿ>uB2dÌ,ûöî¶ûeˑˮG§²ýÏÍöî­Ûu´ƒÊÚ˜À5µÎùü†+ «™×Ztþà˜,:ïsíê%#|È)?/—2å+ÛýK•© »¿%_||'h¬æO<Õ@4»{æÔí~#¾øÎHÛŽJýÆ-¤Ëëwç¯~0[éÖë]× {ìáÊ?úà-™9ÿWÇ^w«9rå‘I³–Šf{S@ðgËþüíro p·\oÌßÐ9¾tråHÝ÷¦kß›®OŠï”({˜Ãª…v¥úľõ—‹¾©_ªP¦poAè(]®’<\ªœT©VÓ¼]îN±Üá‡oGË{o¾fÎ:ì³qÒè™çbù 8 € €Ü ×ûô·ƒÊÎã—(YÖ^=wöŒ]_¾d¾]×>užnb¯[âºåódâw_™¦Í¬—ÇŸ¬''O³ºH–,1À,Tä!yÍ5ǰ–MZÚÇ­rýÚµÐ6Ý·ö.Ýß”% f‹5?u§öÏÈÙ3§íëyý÷ÜæÈ¶78*Ì÷VÞø‰XÖy´o\¿nµíìÿî‡#*;A¨#€ø­e¿ýj¹1Drëºë!§Q£Æf‰¬î«Ç ø†À_Á‡£TÖ«×a¾âzÙ¹ýÎpoá݇þx ‹þè¢CM½BôÇ_*›s“é›ì@@¸+Шió¸{ñ~våú’iùJÕ½«Gª× ·OT;”q½äê­äÉWÐn¶†JÖ†Ã÷Ûí=\Ú®{V>øèsÑÅY4;Z3|µ8éìÙºêâ,çΑc‹½kÇV1¸ŸssŒ×­ÀvxŽ/žkÎéB!º%NœD†>^ê>VÆlÓ,o«hF³54¶Õæù©Ïh©S§õl6뚉®î:ä¹}NÏ•'Ÿ©;ÿc̓íl£Ž €€? XöÇo•{B€¸yë–Üp†zÈõPÏÅ ÉÇ‘øÔlç›·o¬Ÿ¯ÝøvÇpizm‘ HVˆÀ+¾v¿ž×³uóݹÂõA¾Xq÷áÑ.]º(×¹C¦?®4{ººLš¹DŠzô÷<~l®¯]³Â>]‘b%ì:@@ˆº@›:KóVí£~€Ø3mºô^’Ð5Œ´·¢C4[¥p÷€®ÕÚ§ó\ÁG†Ö-JíËÏ•ïÇŽ’닱 uD.D_Šîú_ÆtDú‡ÖGƒã}Þ$ƒßÓ­‹œuHï°JÁBEÃÚl²ÕGË_Á‡B–uøëD‰‡y 6"€ à/aÿ«ê/wÉ} €"pKË×oD6žìÖÿÖM˾òÇE‡Ù²J‹Ö/Ê€¡£¬Õ€øÜºi½}Ÿí_îêõ-ó›®)‚”ÑŸ}$Soúkp¹{çç]óœE,ãÙ>É=ªè›íVVAþ‚ELVõ=:‡E@@¸úï•)yò"ÔÏêtÄõüboÙ·Ö6oŸÙsæ¶›Üg×#R™0~Œ ùðÎÂÏ´l'}ßfvÓìäWÚ6•õkW{=Œ¾ä«/Ç®^±Øëv_jÌžÓ}t(½ötéïÌ}ÖuæÊ“?¬Í®ù­ïNÑtýzÈ¡À%&¨& @üJ ¾_Ý 7ƒ¸€ ,kÖñf®å¨|ê1(¾!ðdžµö…èüÁV~ÿm•}Ëž3¤™Ýð_%A‚æmñÃÇÈsm_±7ëÐg:l›/”ín²/£|¥ªv  € €ø®À…óç#vq®á™#SÒ¤Mgw×é¨"SÊUxÄî¾pÞÏòÏ…ˆ]ãíÛ·eü×#M&²¾ˆ›=G.û8o÷ìèT®U§ 6ZfÌ[#ë¶›¥×ÛÚý}µ¢/ô¾Ñí%·ËÓ¶ïõvkó¶rþÜYoÍvÛ‰ãGízòRØu* €¢Ëø­sÏ à·7]ÙÆ×o¸L#9¶³¿ƒrÿô¥€ß½;|òCÅKEú¢Î=-Ë—,0ûeË‘Sœ?B8¶É5ÿï¡wÞv¯\õQÉ”9«Ù|ñŸ ²xÁ/¦®sHéÜT'Ž•~“m[þ}A»$ƒë ðÇk?-•«>&I’$uÖ­~áü9Y±tlÿs³h&ö­[7%OÞR¦|eiÔ´Uˆ¡É4ÃךÃJT¬DØ÷?~|éÖû]Ñ·ð­²jù¢P‡ÅÓ óWF´ÎÍ´g‡äÍWÈ57[U)]¶b¸Åúݬ\¾Ðü¹e£œ9ý·¦\çu~¶ÍK!ææúÓ1¤··ôeŽåK湆õþÍ5ßÚ>9uê„d̘Y*=ò¨T{ìI·}¬{ã@@bFàF(ÁÝ ½»bæGÉW °¬\¶Ð´þuäÇÖ»«sþ7Õõœ0ß4tèÜC ¸†kv¾¨ªâ‰ß%/wéuw§Pj›\/-Ø·×ÞZ±J S×ç´ù¿Ì´Ûœ¾P¬mv£«¢ó-ûzéÛ»³=„÷)RÚõ)¿u=³Ö—šµê†z Η½uÚ³k»Ý¬ß@@ °Èß>÷Ž~' ª›®Œe+‡Vô]ê°¶ß&c94ºXm÷ü£`áb‘>ÿ¯«—K.mÍ~-Ÿïj`y耾v{éo;íóüá [ûëê;·m‘wÞèbo·*“&Œ5AÐï§Ì ðz–% çÈ›¯¿ì(Ö>:Ôš>ä9D¦Î^!iÓÝ¢l›c~i[:Mš»oõ{ßZ×ýu~.ë“ýA»­MöçµkWåË‘Cå“¡ïÛmVeÔ§ƒMuðÇ_I³gÛZÍnŸ{wïÞ¯½h‚ãÎ œ×2êÓäÛ‰³MÀÜÚ¾iã:«*•(m×µ¢sD¿åÊÐ kÏ2wötÓ4qÆ"©P¹ºçfÖ@@@ ŠÎç RÚs„$ 6ÏûïÿGñ¡î¦/ØZeÖô‰¢Óy–+W.Kÿ·»ÛÏP¯¾~gë³åºõ›ÊœŸ§™]¾ød°Ô®Û8Äœ¿ÎãýëÒ{äˆv“>3YÏ—ûÏLúÜå-¨¬;þºz™½¿/VfNýQÎýŸ}i3æ®6ϙڮ¥{§6²ü÷]®a±3Ú}œ}áX³¿S¦Jíl6õ_W-µ§6*æzáÛùg'Dg@¿þÀMs‹ €€¿ ÜrÍ7{]‡Áv=ë\ËÞ>ÃÛ~“À²Oüñضõû:4«7a¢DözD+[7m°»zË”ÕÎÌh}«;gî¼ö>ÎL[ >{ *[sýÐ0÷¿7¬6ýÔ}:´idÿ ¢múC†žË*Ö€¬³8ç—._1âÃG;ïÓ9Äœûàþ ©óhi· ²^‡óZ´_Ÿî\ÙÕw2tÝ*³¦M”ÚÕKº•³dÍ&:o—U4kàÝ>¯ŠÎý¬E‡œ[·öîÞ\s,[eýï«å™ú5Ü‚Ê%J–5>VýlÙø ÑL  € €ÄŒ@)×HEVYéYɳ|ôá[nÿ?Ýs{tÖëÔob?ƒè˶ޞ=fLþÁ~†Ò€¯ó9­s÷·ìÓëóÇÓO”­É[9þ¬´mQ×ÎÖ>C>ùÆ~!8Uê4ön—/]2¿!Ø ÿUôú¾û…g³Ï¬ëhTýÞìj_O÷Þý$¿ë¹«ïÃíg5u ëyVwîÝíE×ÈZî#¸é~#÷³]íÑZv  €*@Ær ~óÜ7ø¥À-Wëø)Pþðåê0ÍVÉú`vÑLÙðJê4iía¬µ¯f[Å3SÖjßëÈ”Õn<Çü`ÎL[í¯Ø7ß,MŸ}^&Ld†‘nÞàQ{ˆ±ûöX‡5Ÿ¿ûJt±ÊGŸ|-Oå*>bù}ýú5éüÂþº“Î]¿q I–,¹9Æ~—eû–OË‘CÌúúßׄ9t›éÄ@@@ BÎ磽gž *T®&G]C>¯\¶@~ž1)BljJ'i©s·>2äÃ;YÈí[Ö7Ï&:N¢Ä‰eÙ¢92øý7íC¿ðJ7»®‚GŸ­¬ù„5ø©Ác}ÆÑû*\´¸É¾ÝüÇ:Yºh® Ö}_êÔCJ—«¤USrçÎgUM¿W_n)Ï¿ÐÙ<'éðЫW,±ŸÙ¬Žšå|âø1I&$MšÌjŽò§¾hÆ©ÏN@IDAT¬/ÜF¦¨Ÿ>¯i XG”R-ú|סKOS×Ì⮹¢õ9P‹ù­/ 7lÚÒ¬{þG3žõ™¯^Ãæ¢/Ø¿Ç|Gú|§EŸ‡õ‚ €@  Xô?Ü?ø•ÀÑ“•ýå Õ!’­òˬ)¢Kx¥wßöüZ¸tÙ)ë<Žs.©’¥ËÙ›<3mõ}Ò¬%nÃU)VBžkûŠ|ùù€ðÅÿæõ šqí|#ü› ³äÑÇëØÇO0¡è$V`Ùú!@;hÐY3 ­òÐÃîÃG[íÞ>ÿ ¾;GYÚ´éí.o÷êdgè°à|ô¹½-A‚æÇ—/¿›. jÝÉ\Ð9¤­¢ÙÂ^ln­Šf¼þÆ{öºVžª×Øüˆa iý¯kè:-Û·m6ŸúŸÒeïþ€³hþlûýa£y«öv?­äÍWPžiÙN†z״ǯƒØS@@@ &ª=ú„Û4:Ÿû ÄaõùeÙâ¹!Úc¢¡+x«Ïk:m–ýzy=l§›˜g.ÏM[õ-v«/YƪºeÚjãc'»•­Ž€¶J¶¹¬ªÛö¯õ|Ç-¨luÒa¤uñ,š­ë,úF~DŠÓ­À®öϘ9‹ÙM‡…³~P£~~ìõpEŠ–°ÛûKt^3-?|;ÚÎÖaÉ=ƒÊÖNu\sYÅB-´áÈ÷ìÚnu•3§ÿ6Át»á¿J“æÏËO®`þ¤ÿ-•ªÕŸðÜÌ: € €) ~û¾AD»1Íž4k©8Ÿ¡œ»õzûCùpèv“çyôåÔðŠf[%q’¤VÕ|êHEcÆOõÙB³cû¾?L†}þ­Û¨R΃èÜÌK~Ý.Mš·q6‡¨ëóà¢5Û¤u»ŽöØÎNúâ­Þ¯·¢Ãp:f‚¼ûáÑç:o%"žûŽŸ¤»lOž8&ýߺ›ÍÝ®CW)U¦‚ç©Ìµ[Óé ͽñ5ЯÞV?çAt*§ñ“æ„:ÿ´³/u@A€Œå@ø–¹Gð{FMïfSúýÍÀ îܾվK}° ³s8·­[6ZÍR¦\e»îYq9]¬x){³3ÓVß’Ï_ °½ÍY9uò˜½šë¿ù™O;êöÖx£gZÛ}<+k6ÝêÙÙ¾më&{Uè‰èðj:?™UÔ­ÄÃw峦ýh5›,`0¾b7…Z±~ì˜9u‚ݧe›vݳ¢?´xþØâ޼X‰»¾I’Ü}‹^³ ª•+ m^èdÈ…\C×é3ú6“&L”ȳ»YÈù½îH# €qX€Àrþò¸t@ÿøÓ®ôH é7À{†mXw¿Å‘‰Ú[øú¶¶•­™ÃúÊUœ™¶5Êjñ¹Á1dwчîü°°`îL»ŸÎó•#gn{="•­Žù¥5C8¢eÁœYv׺®ìaÖíâ?Ìä% ±÷ÓÌq².¢Å9¹¾ à,êµ`åѸgÑ€ÿ÷ãFIëfµ¥Ý³õäÚµ«ž]XG@@@@b]€ŒåX'ç„ €„-°á÷5v‡Ð²í^*Οs˲ufÊ:»¯Y¹Ô^uÎEå™iZƱsÈjg`Z³o­R¦ü­µÞ§S½ýÏMv·‡.m×ê è×ÓÞ¬sˆU­ñ„Y?{öîgÍžmëÞ­•Ý/¬JÆÌYÍæS§NØÝ*„`·;xTœÃ‘;}­nù 1Cø 6Z4þ몥²pÞÿÄé·zÅb™ûótiØ´¥µŸ € € € € €À} °|_Ø9) €ÞNž8æX´æ­òÞÛ{ëÎí[ì ž™²Ö†«Wÿ•i“¾³VÅ9?sX™¶ö®ŠsÈî‡K•³79ý·]O›6½]wVþý÷Š è×Kþ½rEÒ¤M'o÷j6[CsëŠ ;OøÃ¬­\¶@–,œcþwÙu ²[¥¨k°°†¸»|ù’ܾuË ¡mÍë|þÜYkwI›Þû½h‡/?*A{v™¾oö,éÒgçpäVæõíÛ·å²kî6-‰'–ĉ“˜ùÛtØk]Þu {¾Å5x·Ž­ílp㌂ € € € €Üo†Â¾ßßçG@À!à çÈ•Çt =˜éØÍ­z`½þàƒ9캳òýØ/äôß'í¦ÂŽ9¤þÜú‡ÝÖPÜ›Cv[™Õ8Õ¡œ­òWða«êöùñGïÉÄï¾’S~›7oÚÛ¶9έY¾:ÇUXå÷_WH——îfó¶|¾ƒ8³ƒ/\8oï~òø1»îYùmõ2)ž7­”ÈŸ^Ú¶¨kov¦ƒ´Û jëÜÈz/[7o°¿3oÑk6¶žC—¡ú:cêñâÅ5oÞª½½-ÓCŒÛ T@@@¢% Ï®@ˆ¼Ë‘7c@î™ÀÖMìc—-_Ù®G¦rÊ•õl•¿C9[m“&Œ•Áï¿i­Já¢Å%yòìõ­®ŒY«XckÝúԇ𵿮´VÅ Lk`T÷Ñ«–Eó–;v·ûieíšåòÍèí¶{Øug°ºôóÛ•® ñø¯GÊW_ ³[5ùÝGØëZÉ;Ÿ½>Î éÚ³¯É¶]•Ã÷ËËmïÎuÜý÷ìÍÅŠ—’•ËšõsfJãgZ‹Þ£UÔ·{§6ÖªtïýžÄ_œÃ‘›Ò¤3}®]½;_òáCûíýœÍ&Ÿ5m¢Ý¤™Ì@è èKLçÏŸ— .˜Ooõ«Ž¿££w¶»{§wv‘!C†KÒ¤Iïv¢† €±.PÆõ¼tìÎó™ó/Ö/„"€ Ç,DZ/ŒËEðoM×Ù7ø°+s5*ÅšX÷;{ºþ¡T¨R]ØgæñýyÆ$·Ã–-_Åmý÷ßîŒCŠ[³w­ÌdÏÀôÓšÛåAýßÄI’HEWÐWƒÑKÍ‘aß±Ï÷Ö{C$KÖlöºÎ5l•™S'ÈvG³¶Ÿs M}Е‘í̶Öv *;I%J¬«v©U§H×;«öí•Ní››àr®<ùäı£²lñ\“mlíбëâœKùɺ eôg™Íš™üÞ›¯IC×<Í)S¥–m[þpe¿m;<^ûi3œµvvfž—¯XÕ:¼"ÛZY<¶Œ9Dtîçô2‰fwk`]ƒîA{všnmÚw’ 3[»ð‰´ÀåË—åСCrôèÑPƒÃ0ö4¾xñÎ4¾˜"E -èl¢=·'J”ÈW.Ÿë@@À/(ûÅ×ÈM €IJåXçt €„&pË5¿ï†ukìÍÎyíÆT<3?ú¾È)Œí½YÅ%J•µÛÿq ­X-a ŽÍ5¤³U<Ó ›¶”‰ße§ÿ[ݬ®nŸ¯÷é//¼rw›‹ûËîsäÐ{ža»ÑK¥[¯w¥Ók}ÌÜÈž›užfÝn \5¬‹·òR§Òó­Ü6éwPçé&&@¯&Œc·N®•G¯##¿šhݽuóF»‹sþéÜyó› ¸á­E‡Ãö6$¶n«Y«®=÷´®S@8uê” >|Øëçßÿí7èÖEå-9räB… …XråÊÑCÐ@@@h XŽ;#€ s'޵³_õ¨š •’¿`òé7ÒÿíînÇÓcép_¯»†z^½b‰U\üá2öiöïÛc×ËUxÄ®{V‚vï°›œimL—>£Lþß2yñ¹†ö9ìήJ¥G•.Ýß”ŠUj8›e_Ðn·õÐVòä+ 9så“ÇjÕ‘z Ÿ‘4ÿ 3ZÿW{ô5Y¿}{wöÚE‡·ÖḠ*b»¾ÁþÉè$EÊT2eâ·!¶kðýµïˆfi'td’íÞù§Ý·xÉ»¾Ú8høyá¹úvàÝîø_E³—»¼þ–™gÙyLÏ~¬#€qI@_žÒ ª· ±ÕvåÊ•(ßRªT©$uêÔöâ¹nmóÖnµ%L³Ç7nܳgÏÚ˹sç캶{®;Ût›g9räˆè²xñb·M:¬¶·€³¶¥L™Ò­/+ € € €DG žkXÊÛÑ9û"€ļÀè©%(ø¬”+žKÊÏó'àˆ!pýú5Y¿vµè|Äé\s<æÊ_4c66ËI×|ÏÛ]ÙÍgNÿ-Ùsæv„óJÖ³Çæ%Øç:wîŒìøs³ìضÅÌ“œ9˃¢Ãçp]WDÊÅ.ÈŽí[̜̙2gqÝOÉ废QDè\ŸløMvíøÓØè¬ÍÙrä’쮥D©r’,Yòˆ\}üP`æ’-rôÄ9y²b^©U)¯Þ!·äÏ—.]’;vÈöíÛe÷îÝnäàààHߺ|5W—œ9sÚõlÙ²… ûÛP–ú˜î 4Ÿ8q˜îÚµËþÔ¶ðʃ>"è\±bEI›6mx»²€àù+ ¾nn°xþ²)¨ €ˆÙW²#|Z:"€ €À½Ðù†+W}ì^Ÿ&ÌãgÊœUtñ…¢™ÍêUÍZÖù’s&Gõ¾$H šVVxTÍ~ €À½p­@²~S¦Lim柨Zµªè2dÈ™3gŽÉ|ôèQÑlf],-[¶4æråÊùì½pa#þøc³4iÒÄ™7n‡îŒKõWì™SIÅâÙüõö¸/@M»Ž›À²G3« €aX‡M € € €@d4¬ÃZkY—àà`¯»*THyäHÖ`rΜ9½ö£H‘"…=¶f,Oœ8Q~üñG“±?}útÑE3—­,f†Éö‡o{@@@ °ìß*÷„ € €±" ä5kÖ˜ òo¿ý&ðzÞâÅ‹›ù‘­¬älÙȈó E£ß è<˺¼ñÆ&¸¬AæM›6ÉúõëÍÂ0Ù~ÿG€D@@8,@`9y\: € €Ä®ÀŸþig#k yïÞ½^/ lÙ²fŽd++9kÖ¬^ûш@  èÿ&zöìi–É“'› óìÙ³&;Pÿ@pß € € ',lj¯‰‹D@@û!pðàAY¾|¹¬^½Ú”wîÜâ2$H`‚È*T°³’3eÊ¢  à] y󿢋fÿ[ÃdŸ?Þ ‘Í0ÙÞÍhE@@î‡åû¡Î9@@@Àg4¬ó$ë§.ž%Y²d&\±bE;+9C† žÝXGH T©REtq“½mÛ6†Éޤ#Ý@@@{%ï¶«Ü«ƒs\@¨ ŒžºQ‚‚ÏJžlé%·k¡ €†À¦]ÁrîÂey²b^©U)o`Ü´Ü¥••l“uÝY’&M*Õ«WwËJN—.³ u¸·nݲ3˜çÏŸïv†&MšHË–-¥qãÆní¬ }þZ¾r¥d}àŠÍË‹BQ1d@ . ¬ØxX§Í+Ï6¨ÁóW\ü¹f¸/d,ßvNŠ„-pùê I–4‘øë´YÂîÍV@Hõ@9sáŠ?Ý’OÞ‹f"[doYÉ… –5j˜€²~fÉ’Å'BÀŸâÇ/Ï=÷œYô§Ö0Ù—/_f˜lþâïÓ½íÚ°D¦¬sŸÎÎi@î—Àͺ¹N]ã~žó"€qN€Àrœûʸ`äIÊÑS×áV¹G@ —®JºTÉ]à‰'ž]œÃdïß¿?Ä0Ù-[¶”²eˆ~ ¶ € € €„)@`9L6"€ € €€/ œ:uJ,X :ô­•5KÙY’&Mjæa­^½ºùd®d§uü[ páÂòÁ˜óĉåÇ”•+Wʉ'äã?6K›6mL–³Î×LA@@ˆœåÈyÑ@@bYàèÑ£2þ|PÖ òùóçÝ®@ƒI$²‚ÉY²dqÛÎ –@Š)¤C‡f™;w®=L¶*|ÿý÷fiܸ± 0׫W/°p¸[@@@ –£Ç® € € po4YƒÈºhPùÊ•+ö‰âÅ‹'O>ù¤H&+Ù¦¡‚uêÔ]zôè!ß~û­Y.^¼(3fÌ0K­ZµL€¹E‹{²Š € € à)@`ÙS„u@@¸/{ö챃ÉP¾qã†}‰%’Úµk›€²~æË—ÏÞFO T©R¢K×®]eܸq&À|üøqY¸p¡YFŽiÌíÚµ“ „w8¶#€ € €)@`9 ¿vn@@ßؾ}»•¼hÑ"·‹J–,™HÖ`r®\¹Ü¶³‚DV þü2pà@`Ö f 2ɯ¿þjg€9uêÔ‘=<ý@@@¿ °ì×_/7‡ € €€ï lÚ´ÉÎL^¾|¹ÛêܨD¶–lÙ²¹mgˆ ‹ýÍ7ß”W_}Õ"[ÿnÚºu«tïÞ]>ûì3iß¾½Ébæï¡˜ç € € à–ýá[ä@@ðquëÖ™¹’uˆkÍ t–4iÒØd;Y>@ 6ôe .;Ì«V­’È;ï¼cÌ:<¶™ *—Ä9@@@Ÿ °ì³_ † € €@ÜX½zµ™¼~ýz·›É!ƒÛœÉºNAî§€u™:uªÉbž7ožœ:uJ† "Î!²Ë–-{?/“s#€ € €÷M€Àò}£çÄ € €øŸÀÒ¥Kí`ò–-[ÜnP3‘5#ÙæZ3•) €€¯ 4kÖLtÑÀ²Îìæ+W®È¨Q£ÌÒ¦M€®Q£†¯]:׃ € €ÜSË÷”—ƒ#€ € »›7o–sçÎIlûì3iß¾½¼ð ’5kÖ€qáF@@@ÀøÈ¿[î @@ÀÏ<ƒÊz»¹sçŽÒ],ßÿ½™kSç€Ëž=»<òÈ#Ò·o_)V¬˜u¸Ÿ7n4?†íÚµK¶lÙ"'OžÍ®(P €™C®yóæR¦L™û 6L¶mÛf·W®\Ùüxf5 >\öìÙc­JË–-M†óˆ#Ìütû÷ï7Û4h¢÷¢A—4iÒØý© €þ  AåvíÚEêV¾ýö[ÑÀ2@ n<ÿüó¢ËرcMóúõëe÷îÝÒ£G“ÁüòË/‹.É’%‹7ÄU"€ € €€_ Ä»í*~ygÜ ‡FOÝ(AÁgåÉŠy¥V¥¼‘¾ÍPÓ ²U^xá ]µj•Õäö©:Ç›‘ƒƒƒÝ¶Y+Ó¦M“&MšX«æS3“u¸·ÞzË­ÝÛŠë÷ÑG¹mªS§ŽÌ›7ÏnÓ ˆC¬R³fMYºt©µjæ7nœXe{à foÞ¼ÙÍ=·±ŽÄÏ¿ÿ£T¶î“à²%Á' ÷¾üòK`Þ´i“}ñ:…Œ—5‹9iÒ¤v;•è xþûý#r@¸ Àßÿqá[â@À×?Ô×¾®¸šùZPYO§ÙåË—5¨¬}š6m*.\Ъ]>ýôÓ•u‡!C†È_|afN‡TÖãýóÏ?&CO‡È¦ €q]`ùòå‘ÎTvÞ³f9k`š‚ ÷4€¬YËúÿŸ‹/nn`ÇŽòÚk¯I¹råÌK¤×®]‹{7Æ#€ € €@œ °§¿>.ˆœ€fôêðÖÞŠ_­Eû,XÐ[™0a‚Ý~ýúuéß¿¿½nUtޏ.]ºH•*U¬&ûÓ™l7F¡¢×X£F ¯™É@?xð`ŽÊ. €€ïìÛ»C5jí ÒಾDAˆ{ $N:Ɇ Ìßåš±¬E§’ÑŠ4À¬gEˆ‚ € €ĆåØPæ €€|øá‡rþüy9räˆÛðÓÎK{ã7äܹsf>·Y³f97™úÎ;í6óM3„E÷ùá‡däÈ‘fžfÙYV®\é\R½uëÖröìYY¶l™ìÝ»Wt˜oÏäÙÄ: gNýµOzwneþ>މ‹îÖ­[´2Ÿcâ8 €@Ô'N,]»v5Ì#FŒ°_ݺu«y¡³lÙ²2zôhaÔž¨ÇÄžšA~õêÕH/ÎÌóC‡ÉöíÛí寿þr»4}‰ÀyŽ›7oºmg%ò:€Ó\Ÿ5) € €¡ $ }[@üI@ƒÆñâÅ3·T»vmóƒÔž={ÜnqÀ€?þwŽ4h ™2e+“Y;ž9sÆî¯Ë:¿›Uô¯zõêY«¢?rh›³x¢Û"R×LeoN³7´èµvîÜYt¨ogÑà9ˆ«³å“QßÍ–én™à²Î¯e¹khl-º®/ E¦XCb;ç±ÌþôE¸ÿÉ“'—îݻˋ/¾(cÆŒ1ÿ¿xß¾}²eË“Ùüõ×_›9˜umJì è ¸VFydϬ™çëÖ­3»uìØÑí%à¶mÛŠóßî)ðöÛo›Àrh§Ù1™µì™­ÚyiG¸#‘à3V €q[ cƌҷo_;ƒùÓO?'vΜ9fy饗L€¹H‘"qûFãàÕëóPx%Mš4v—ܹs» ©ÍwfÓܳо¼¬sW[%gΜV•O@@/–½ Ð„ à]À™±«éhSÞ¦.Ò ¬.:J•©®Yï:§·³Ì;Wôz­¢A_ ÊZe«ý?þ]>þøcÑgHÏà®ÛÞ©S'™4i’µ‹ý©÷¥‹½{õê%3gÎ4Af»ÃÍ:ÖÀ³3Э›ôÞÖ®]k xëËï¾û®ÛîãÇw[¯Q£e7V@@wøî«¬!€ º€óMu}{ÜYf̘!ÇŽ3M[·n•.]º¸ýÐ`õÕ€3@@bG@§¦Y¹r¥ 0j6³ >ýôÓÒºuk“Ý;WÂYbZ`ðàÁnAeÏãkp¹M›6žÍ2a©S§Ž× ²gg}1¡lÙ²råÊÏMnë:Ï·gPÙÙAƒ¼mÛ¶ÏçAý3è-¨ìÜת7jÔÈ©­uýܱc‡”,Y2DPÙÙǪëPñ?ýô“µÊ' € €@ÈXŽ» €ˆ<üðÃæíuËB(xðÁÍpeÎ7æ­íÖç¿ÿþëõyk;Ÿ € € ó;v4ó/öÙg&ƒY3E5Àèœ9kÖ¬1â=â¸qã̰ÌaÝ~ݺuE—{Y40¼aÃÖó\ºtIzôèâ”Öð×.\0Cj;;hÀø‹/¾ž={:›½ÖuøiF[‡èö 4kÖüÁƒí¬eÍdÖŒhgÑýuïk×®ÉêÕ«CŒ—-[&:¼¶UúôécUÝ>Ë•+'‡ x׌}ÍnN [V@@"&@ÆrÄœè… à! ÉúÐïYœAeç¿ÕoÅŠV•O@@ˆEÎX³6ûí7Ñ@³ à 2Dtj2›3D=zt˜‹áÝÒ A 2óïÝ»×d¢{óý÷ß·›>ùä“ÁVN{÷îÝæ%çX‡¬ö¡Jqúôiû8Þ*š}¬/,hðW¯å…^ÑM¯Õ*:²gÙµk—1;v¬èHXž×áÜGϳgÏv;„>ƒêuj?CZ³¤E_ˆÖyœ) € €Q °57öB|Z ~üðÿzOš4©Û=$K–ÌmÝÛŠó¸šÍ°xñb)Q¢Dˆ®úð¯?Nýúë¯!¶éiVñ¼Ïõ$I’X]#õÕý"u:#€ € GtHl{W‡ÈÖ¡²µhvg·nݤråÊ&ÀGo- .[³Œu¸ë|ùò™ûΟ?¿YÏž=»›ÃòåËíõ‰'Úu­è1ôÙ-a»ƒêèTÆ së§YçœÊn]+úÒñ—_~igë³cçÎ=»É‘#Gì¶ôéÓ›9¤ui]4›^GÁ²ÊåË—%C† Öªù<þ¼½îíz~üñGI—.é“(Q¢s*ë³™‚ € 5»ÿ¯1jû³ €€ h¦AxÙÖÛóa]þÂ… ÃÚ,åË—7s²éÐjúæ¹>¸ëüVÖÜmºóíÛ·C=†ÎËV™;wnX›Í6ýÁ"¬s„{: € €  U«V]t¸d}~Ð,[ÍfÖÅ"û‘G Pß¿m}) uêÔnªYéM›6ÍL¶Š…Ïœ9#©R¥2s[íúùÊ+¯ØÁ`g{óæÍCÌϬYÍ¡zÚóeeçs¡µŸsŽåfÍš‰.Z®^½j†¾Ö?‡:\¶fM;âÖþÎOgö³¶k@½páÂÎ.’'O“=}óæM»Ý3 ÚÞ@@@ \ËáÑ@ ,}³½bÅŠf «Û@@@À74@©‹¾|ª= j°Y—W_}Õd2kf+%âÝ»w—Zµj…¹C©R¥ÂÜÞFo[ÝÇ[»f¤k`Ù³„ö½&NœØd3;çIÞ¹s§çîöº·‘¬þøcñ ë5öìÙÓ¾Ô¾}ûÚu«R¡B>|¸ âΚ5KêÔ©#³g϶6›Ï H™2eÜÚ¢³â9³ë‡~Ë—/‹¶—/_n2å=çu¾uë–}ÚV­ZÙu«Ò®];“!C†Èßÿ-½{÷ÖX?Ÿ~úé@æ¹/÷î-Xl]ˆÎY\»vmkUt½}ûö2nÜ8»M+Îà³Û†ÿV ä­9Êm &”råÊÉúõëíc,]ºTxàI™2eˆa¸­Nx¶Jž´¢Çס½=‡ÅöÖ?ú¨™;×—®•kA@@ˆ XŽ˜½@@@@"! óü~ðÁ&ÀÜ´iS³çòåË¥N:Ò±cG9pà@$ŽFW@@¸ß–ï÷7Àù@@@@?Ð9o§N*ßÿ½<ôÐCæNÇŒc†ÇþôÓOýøÎ¹5@@üK€À²}ŸÜ  € € € à“­[·6ÙËo½õ–$L˜PŽ=*ݺu“š5kÊ‚ |òš¹(@@¸+@`ù®5@@@@{(:uj0`€ 07nÜØœiéÒ¥R»vméܹ³:tèžC#€ € €@t,GG}@@@@"-P¡B™>}ºŒ?^Š-jö5j”{äÈ‘‘>; € € €À½ °|ï9 € € € €€çŸÞd/÷éÓGâÇ/ÁÁÁÒµkWyâ‰'dÑ¢E^ö  @@î—åû%Ïy@@@@$mÚ´2hÐ `nذ¡Y¼x±ÔªUK^}õU9|ø0J € € à–}àKà@@@@@¨T©’Ìœ9SÆ'… 6Ÿþ¹T¯^Ý´º÷ € €Àý °|¿¿Î € € € ` ´k×Îd/÷îÝÛ´@_xüñÇ¥bÅŠ2|øp“½üï¿ÿšìeÉcìØ±’3gN_»d®@@8)@`9N~m\4 € €!tNå²eËšÅs«–­ ³~:‡Ù¾té’ÝýƲqãF³ØÿU²gÏ.… pΓ'gWÖ@ˆU)R˜ìe 0<Ød//^¼Xôߨ¡C‡Ê믿«×ÃÉ@@ðGËþø­rO € €xdË–MtyôÑGݶèpÙ΀³Ö÷îÝkÏGŽqë,ºèõÎ’8qbÑy› ( ùò哼yóšO«®YÖ@bCàÉ'Ÿ4s/1 ‡­ÿÎõèÑCþ÷¿ÿÉĉÍ¿…±qœ@@ °ìß*÷„ € €@âÇo²5Ù³œ?Þ «­ÁfkÑa¶u~ç‹/Úݯ]»&Û¶m3‹Ýè¨XfëÓ 8ëgÊ”)=©"€ }4iÒÈûï¿oÌÆ 3ÙË+W®yã“O>‘×^{-ú'á € € åüÒ¹e@@"":uj)_¾¼Y<ûkæ²™g×Y—›7oºu·†Ývküo%cÆŒnÙÍl¶Ï>ø ·]hC@ BuëÖµç^4hÙ§[·n2mÚ4™2eŠdÍš5BÇ¡ € €Ü °ÌŸ@@ˆ´€f}éR³fÍû}Úš=Î|Ö,hg¹råJ˜ClçÊ•+ÔÀ³ƒJA@À¨_¿¾=÷ò!CL󫯾j²—Ÿzîm×z<«+Ÿ € € ŠåP`hF@@˜ÐÌ1]tˆmÏrõêÕÎάgËÙY:$º,]ºÔÙlêzos:k¶³fZS@O sæÌòÑGÙæ5kÖÈŠ+äÌ¥ÛòØ3owŒ € €@$,GŒî € € po’$I"EŠ1‹·39rÄkàYƒÏgΜqÛE3£uY¿~½[»®$NœØm^gÍ®Ö`sŽ9Ì’-[¶ûЀ à?7¶‡Æ1b„4~î5ùçVbÿ¹Aî@@{$@`ùÁrX@@ˆY+ð[½zõÖ¹›s9;ëšÕì,šù¼sçN³8Û­z¼xñì ³3àì¬gÉ’ÅêÎ' €@Ð9–‡nÌK.ù'øl¼ .@@Ø °»Þœ ˆ5ýÑüöíÛ‘>Ÿþ˜®™\Zô‡ø‹/ÚÇÐù*Y\7nÜ›7oÚÛ&L( $°×©Ä¾ÀŽ;ܾwýÁ,mÚ´±!œˆeý7ªL™2fñ<µþ{åmNgk˜mËÙYôßÏÇ›ÅÙî¬ë¿yV ÛpvÖ3fÌèÜ…: €€ 4kÖLFOÝèƒWÆ%!€ € à{–}ï;áŠ@h hVÑ¢E£tœråÊɺuë̾;v”yóæÙÇiÛ¶­|ûí·özžßtîð5ÑS7JPðYy²b^©U)ôàch×­Ù²e˺mŽJÆr:udÞ¼yöqô=ŽUR¥J%άûôé#ãÆ““'OZ]Ü>ŸþylMž<¹[»®èyš7oîv¼\ Mš4‘¯¿þZÒ¦Më¶¹fÍš²téR»íÃ?4×b”í ÿU4È»yóæÁݳgÏJ§NdÒ¤Iž»„XŸ9s¦ 2{nЬa <;Ýž}t½ÿþòî»ï†Øý=ƒîQùÞC\  kÑýû?Ö.”Å}QÉhvÖ=ÑW®\‰Ö}éÏÎ@³•ù¬Ólè¿ÛÞæ…Ž9;#ÐHÖ—*½}n‰ÌÙüûëM‘6@Àÿøûßÿ¿cîb^€Œå˜7åˆ €@À <8Ì{ÿî»ïäâÅ‹¢ÙÀÎ2aÂiݺµ³)ÔúôéÓeÓ¦M&Ã*¬£ûöíê1tƒÄõ§åË—» ‘­×1gΜ0÷µ66jÔÈd#k´UtÌ’%KZ«a~ê]šýì³ÏÚý¢»¿} * €¸ôe.!#¬Q2,(FÛ 6[Ÿ¡¢uÈnÏ¢é˜Ås[hëI“&5ç°‚Ï΀´³ß<ÚaiGÐÿ/¯Cb[YËÎ[&ƒÙ©A@@˜ °s– ðiÍ$Öa™Ã*uëÖ]îeÑÀð† ìŒêK—.I=BœÒþúÂ… fHmgÍBþâ‹/¤gÏžÎf¯uÍLÖa´uˆnÏìåU«VÉÁƒíÛÿøãAeÝ_‡ðÖaGW¯^" yÙ²eâ ,kÖ¶·R®\9ó£—g6÷Ë/¿l²›­ù›£»¿·sÓ† €@Dt(k]t‹ðÊéÓ§ÃÍ„¶‚Óaëßÿ•cÇŽ™%¬~Þ¶%NœØk´¢hg`Úª§H‘ÂÛ!iC8&`͵Úe`M†v@@¢&@`9jnì…Ä9 ¢êVÉž={´Ë 4ï8ßÿÛ»èÛÆúà;\wý2u…È”©×”¹~X!d¸µÌ²D4²(ÃbYæ9)ÂbÉ,ŠUŠ?M” ±,áQ®)BVµèÊïß{ÿÚç¿Ï>ç|ÇûýÞ{¾÷õ¬u:{?ûÙÓkÃ>Ÿçù<+®X<ñÄÅ¡‡ZÜrË-m§=ᄊ›o¾¹¬;çœs:RggŸSO=µÈ<“)I ½å–[¶µË1’ún±Å+ÛtûŸŒ>N*èmß~ûír~èK.¹¤­i®±ÅÕ-ðþøãK-µT¹Ï›o¾YΫ\×÷Ià¹y¯ :ß~ûíÅ¢‹.ZÌœ9³¼†üÀU•ŒœÎ<ÎúЇÊÀõhö¯Žé›Œµ@þý›Ïk¬1è©2ÍDýóÚk¯µ­×·Õ—Ó.ÿþîUª¹¦Àn™4iRÛHéLï‘Ð 8ç»ùJýäÉ“‡{Ú 0JF-×=æü3i$³Íe*›t”IÉèëdxªJ:É,½ôÒÕj‘lÿú׿Zëy_ª:¨¶*- K £êÏ-ï~é„T•L)Q7Ï¿?’m£WÉ3jN-‘L[Õ»m¯ý†ZŸkÉ5Uežyæ)ÿU­û&@€Œµ€ÀòX ;>æ"h“î:£RVZi¥r=AÓ3f´$’~º*W]uUµX~ç§vZÛ$S§N-Î8ãŒbÏ=÷lµM@6s*ï²Ë.­ºúBF_xá…­ãä…û ƒ*šågžy¦µ[~ ßÿý[ëüà[AåTæ~ñÅo p'mhUêsÔÒ 0u¿^íê#H{µ™õ;d´³2¾õåã{æÞgËhy… 0^Ëã%í<˜Í‡rH™Nz ËXk­µÚ<è¶nÛìÔ­>iÞXn–*-u³>#u²­>OrFô*éIÞ,¥,«Úæ©#Ž8¢¸æškŠÀ‡S’6»^š½ß«mÉÝ­ŒvÿnÇTG€æVüwF>Ë-·Üˆ ’δlÎr‚ ͺj½×¶nõõÔ«ƒ]`Üùd¤µB€À¬¨Ìlº]±Îï;ëìH @€&¨€Àò}°n‹MZ·Þzëfõ,]_rÉ%»¯×<ÈÝ~P(øÛÜ6Pà·9Z¹ë…5*ßxãbÇwtdrc·Öj‚åõ²ÄKÔW]íþƒž@ @`Xùï‰|2Ƭ.áØ-àÜ-HL*Ií]ͯ:’å‘ìS?_¯ýgµ‹ão¤(^ëcÛóü×B³üÔ™žg°’ÑÖUIv†zJíÕV[­Úä{ŒòžœÖUeÙe—­} @€tXî‚¢ŠF&ðâ‹/vݱ™²-2z(©›åùçŸoVµÖëó!§òø@kÛ¬X8ì°Ã:‚Êù¡!)¾7Ùd“"#º3‡Ûyç×õtQ]Ÿ«ñ©§žêÚ®Wåh÷ïu\õ @€Àœ'°À ù(ŒNàé§Ÿ.§›îQPÎÛoºé¦Å7¯ xbÆ«Ã=Ä€í›sõØø?/¸à‚¡4“6éôûÜsÏ•Se¾÷Ñ–dYHÿ‘–tfIŠþ‘–¡î?Ð|É#=÷HöK:ëxu{GÉñÆjŸtŠšÿîÊßGþæFóŒÇê—X`äÿ…7ðqm%@€¹PàG?úQ×»¾ãŽ;:ê]tÑ2=e5¿pÕà¶Ûn«Û¾|ðÁŽÔÔÝRl·í4Ì•›o¾¹m=öØ£ÈÜÊÇ{l±Ùf›™/:ó#÷*+¯¼rÛ¦‡~¸m=+™[zÅW,Þ÷¾÷µ>7ÜpCÙn´ûwœL @€ .àðpJÊ?ùÉOÊ¥ *ÏIåŒ3Î(öÞ{ïÖ碋.ôòþô§?§Ÿ~z™*s.ç)ǸòÊ+Ýwƌŗ¾ô¥²m‚šË,³L‘QÖy_Ùu×]‹Ç»e™ÊÏ<óÌâsŸû\ëó³Ÿý¬x饗УŽ:ªÜ?Ã\Ï:ë¬SwÜqƒÎw¢ýöÛ¯Øpà Ë÷ÄX§L™R^[\rŸ•<Ó.×ëÏþ™!ÇKâú”Jõãì³Ï>-ï¸ýüç?¯oÓå¼û朙BiòäÉŤI“ŠüãÅ‘GYôz·ì‚rý9fõIg…<üën¾ùæ¥IžK–9æ˜âå—_ð™**ÿKö³x.¸à‚¥ïž{îYœþùE¯Îåùû«®!ß?øÁÊ‘á^xa1uêÔ2 œ¿…è?ùúï’]1Ì©×_}qÏ=÷l°AëÎ|Åõ’ mUòÃNö«J–ï½÷Þb½õÖ«ªÊÐüÑ,k¯½v³jÄëIE™Vê%×™—îª$(|×]wU«ßÍÔIÕyÛò"]•o¼±ãGü`’2Úý«sø&@€ 07d´òå—_>¤[­PÒ³¡ÑwÞYÜzë­­3ç]dÿý÷o­7yä‘âÐCmËšô»ßý®È'.·ß~{‘Àb·¦9Ï´iÓ::ïæ Âæ“w³¿ýío—lëçÿáXäz«’Òûî»oÇ»Îo~ó›"Ÿ³Ï>»ì´›,MõòꫯŸÿüç;ÞÓ&Ù ò¹û¯|å+ÅM7ÝTì°ÃõÝËåŒ:N@9÷]/yËûi>guVqüñÇ—†ëmò¾V/élðÑ~´^5Ë—óî™ûùÖ·¾ÕqìŸþô§e§‡SO=µœ¦é;ßùN×ç×±ã*òÜêÿŸHÖ¯éÓ§wdÝʳË'®ºêª2ÐÜ®¸âŠâ«_ýjqõÕWÛl³MÛîyæõëÈ;oΓB€ô·€Àr??WO€9N =ÂO<ñIJrF÷v ~øá­ë>úè£ÛËÙ°þúë—½ÍóÔù1$?ŠÔËöÛo_ö~¯×f¹9sŽ•åü@1ÿüó—åüÑœ×9)¼ª²Ûn»•½¾«õ|§ü /¼Pö´Ï_ùá§^2b{Ýu×-«F»ý¸–  @€LtŒ¤¬ôC@y°{èµý”SN鵩¬O`/AÌ*CRÕ8ÁÊdgJùîw¿[¤³p‚Ø™s¾WÉ{Ý@%ïQép›Ài=Ev®#Áë¡”wܱxàŠzãG}´XsÍ5‡²{ùnš,QŸùÌg†Ô¾[£ŒÊ~â‰'ºm*ëòî7XÙyç‹ÿøÇƒ5+é1KGíºÙ ;ÖTäZUÛb÷[l±EÇúœÞ¿üå/ˬ]m»¬ä¹n»í¶Å¯ýë¶Îáͦ§vZ³Ê: ЧË}úà\6æd~TÈœÅI£U•¬§gû¥—^ZU•ßõàsÛ†ÿ¬œ|òÉݪG\—Tiþð‡‹ûuŒ¼„'ÕWÒÁ5ÊU£¤\«ÊòË/_|ðÁeJ°ª.ßI׫$m]õÍh÷ïuõ @€&šÀ`£•gw@9ï7ÉÄ4PÉ(ÏæHÏÚd[Ã÷ß«3ëßÿþ÷25tóXIœ´Õ¯¿þzGÐ3#T¿ñoƒ½£å˜ywÊq’º:ûÕË/~ñ‹"Ï­µœQ­Í röO‡ÛÌ9œlQÍQÈE[,'mt·’w»?þñm£¹Ó.é—Óyx¤sûæ½°9…Q·ó÷ª»å–[:|sÏÛm·]1sæÌŽN×5žÝc¶=éÂ3 =%óSá _踅7Þ¸XuÕU‹_ýêWe ºÞ ã™F¼~nË @€Àø Ì3¾§s6˜ÈÍù’›÷š9»®½öÚ¶ôÒi“¹°2y¨åºë®këM=ÔýkwÒI'umR*'^/IËVŸW*óTåGŒ¡”ÌK–Ôbõ2ÚýëDzL€ @`¢ ô­œ€òœ0‡r‚¨ßüæ7ü$…óhKÞ£2‚6óè&cT”Ír '´ªÎ9眎`k²*%]rÞ³2·ïC=T4ßírŒ¿üå/­ãt[Èè㤶Ž®å³ŸýlG³úhßn÷Ǽ4»ä’KЇ~¸ã:êû$ðœ@m½$èœëL»¤pΈßzÉ»ÝoûÛzÕ¸-'ÛÕ—¿üå¶óå9ÁóÌIÿܳd„ùhKÔéTàõ÷¿ÿýŽÃ%5zF§$µu‚þõ’ÔÜñ¾øâ‹‹üÝ6S´çï½Ù‘ ¾µœç“ΙkùÀ¬ª} @€}$ °ÜGË¥ @`¼šé¡›ëÍëÉË^›?B¤]æ+Îhàôpn–EY¤Lóuþùç77µ­'`›"vÙe—¶ú¬Lž<¹£n(õý’,é¯Óc¼Y2ÿq^¶ó"Ý,™/¬*¹÷¼pqÄUUÇwŽŸ@ši±Óp´û7O6Ø3k¶·N€ @`Nè6ZyN (§]Fþ&ÝõŠ+®Xžv¥•V*׬¬—¤Ÿ®JsŽÛ#iŠ“Á©*S§N-;ÿVëùN@6z•¼ã$XXNêæƒ:¨£yæü­Êb‹-V(¤Ìç¼óÎ+–Zj©js‘ìP‹/¾xk= ýë_[ëÝ®'ÚE]´l3iÒ¤Ž9•³!ƒgGyî¹ç:‚¯_üâ‹%—\²u9«¬²J±×^{µÖ³÷ì…GZò¾šôÛ™k;Ïù“Ÿüd9·róxI‡ÒôɳMf®ªäÝF¯' =Pùô§?]ÎwìcyÞÙ® @€ý'ðÿÿ«±ÿ®Ý @€@¼ ¥ÇúhË7Þ8à!’&­Y2¿R^Ó[9½ÞZh¡rΫnÁÚú¾ïxÇ;Ê’šìÉ'Ÿ,{ì±rÿ×Xc2 z•*mW¯í©Ïƒ¹ì¾ûîå‹v‚à™o*?Fl²É&m/ûƒ#s2§G÷×¾öµòr/é-Ÿ zæÿJÊëÜo¯2šý»¶^çTO€ @ _꣕gwÊëÙi¶ÓN;ïz×»Ú.!ïO "fdrU~å•WŠ…^¸#…ñÐ Wíó=mÚ´ŽìJy¿ëUÒ ¸šâ§j“ i³dÔnUÒa¸ê4üæ›o–tÏ=÷Ü2]vFM×âÕ>õïúèçÔ' ÞìÈœw¯ŒžNzçªtë]mÊ÷V[mÕ³ÙóÏ?_Ž´îÖà©§žê¨Î=ÔG”§AÐÍ’QÂm´Q³zHëÍ@uvÊ<ÓÍQÇÕˆãfúñ<×d¬$Пwà^%éÔó~­ @€ý- °ÜßÏÏÕ @`ŽHÏ÷jî¬á\dz?çˆn?B ç8£i›\6ß|óÑ¢Ü7ÇÉ,CMÝ<áh÷oÏ: @€~¨F+ÏéåC9¤ØrË-ä^k­µÜ>ØÆ^ïLÝê3ßpÞ/š¥×;[:»f[lÌ~é0Û«4§ J»¡dOzíµ×ÊlO×\sM9*º×ñ»Õ7ÓF§Cr·’‘ܳªÄ$éÂ{•n¸¡,o¶Iêf¹è¢‹šU]×ÿö·¿u­¬2¼ÓÙ YÒ<Ï,鯫R”y䑪ªüN‡ëãŽ;®­®ÛÊË/¿Ü­º¬K°j$yÏF6 @€}! °ÜÉE @€ @€dkæðÝtÓMçhŒí¶Þzë1½Æz åú‰zezê–Ýh àos[F>÷*ÍÑʽÚÕëßxãbÇwtdr}Ÿúr‚åõ²ÄKÔWç¸åÁæ¨è‚GX^yå•{¶žþ<’á+%ä‘”·Þz«çn‚Ê=il @€}' °ÜwÌ @€ @€æN½÷Þ{î¼ñ.wýâ‹/v©mŸ‡¸j°Ür˵ͣ\Õ'us¯RŸ9m2êtV–Ã;¬#¨œ€|R|g:¢ŒèNÚó̽ܭdôp=Ú-Õt·ýfW]Òr7KÒC4MRÕ¾ÛˆðjÛ@ßIÞ«dº¦z©®/Ϲ½œío¼q±Ûn»Õ›v]^vÙe»Ö§R ìž46 @€¾Xî»Gæ‚  @€ @€æv¤dÞwß};î¸ãŽŽºjÄhæ®co»í¶b¿ýöëhÿàƒv¤¦î–b»cÇaTÜ|óÍm­÷ØcâòË/o ´ÍhÜ{î¹§uŒzZçª2sKgj¢þóŸUUqöÙg—óP·*Æi¡[JîŒj|V\VF™?ûì³ÅÒK/Ýv¸XÕÿ²± ,O:µ-°œç~à¶ío…˜{þ7ÇÉÜ{ÿîœ @€ @€@ß \ýõmÕÜ@™¯¸^6Ûl³Öj榮—ãÞ{ï­WIiÜmNݵ×^»­ÝhV’Ú¹9b6×Y½› ð]wÝÕó4ÍÔ ¢^vÙemío¼ñÆržèœ«úL™2¥­Íx­t ,Ÿyæ™§?÷Üs‹wëŸ×_½£ÝP+®§AŸ9sfqì±Çvì¾âŠ+–u«­¶ZÛ¶üLŸ>½­.ó)gTyýÏ:묶6V @€‰)`ÄòÄ|®îŠ @€ @`‚ l¸á†Å‰'žXø2º·[@øðÃo)}ôÑE…õ²þúëgœqF‘ï /¸à‚âöÛo¯7)¶ß~ûbuÖi«ÍJsþæëŠ+®(vÝu×bþùç/ÊÇ|Ǩé·ß~»uÚ¤g>æ˜cZëYØgŸ}Š^x¡ z>ýôÓÅ¡‡Ú¶=#¶×]wݶºñZY`Š]vÙ¥Í?Öo¾ùf9‚:óTßpà ÅùçŸßvI«¯¾z±ð ·Õ gå–[n)6Úh£bÚ´iÅ»ßýîÒùÎ;ïl;DÎQu:Øa‡ŠN8¡µ=ûO|â¥eþÞî¿ÿþ")¼›#Ä«ÀtkG  @€À„XžÕM @€ @€Ì  ÷*™›7é–«’õ¤Ï¾ôÒK«ªò»|nÛ🕓O>¹[õˆëæ›o¾2Eõ}÷Ý×:F‚ .¸`ñÎw¾³# \5úÇ?þQ-–©›>øàŽ@ìQGÕjÓ\8ýôÓ‹pgWIÐ8)̬­Ê%—\RäÓ«|ýë_ïµiÈõI^OÞÜ1ÏwÞyç-«3·u‚úõN øÃŠX÷*›nºi±í¶ÛöÚ¬ž˜@RaO ‡éV @€ @€æŒ¾¨,³Ì2ŵ×^Û–^:í3:9#‡Z®»îº¢™y¨ûÔN꺹tM ¼^î¾ûîâÅ_lUeÄræPJIÚé=÷Üs(MǬMžÙÕW_=äã'à\Oe>äk û;Iªìí¶Û®¶GQyä‘ÅV[mÕV×kå#ùHqÕUWµÓ½Ú©'@€&†€ÀòÄxŽî‚ @€ @ šé¡›ëÍ[KÐ1ÀnÃ#3xÕUWmîV,²È"ÅM7ÝÔ1Ò·Ù0Û'žx¢LßÜÜ6yòäfÕÖëûm±ÅeZæŒPn–ÌŸ|ë­·_|qsSqöÙg·êrùˆ#ŽhÕ5rü¤ƒn¦Ån¶Ëú`æÝö©×ÕﯪoÖm³Í6ųÏ>[ìµ×^U“ŽïÌgýØc•£Ëë3Ò{8% ’Ö¼ Ï1â’ôãIÇ]ŸÛ:Û’Ž<þéTÐíï+mRN9å”"#ÍßûÞ÷þoÅþ·yÏm­ @€}-ðŽÿùwéë;pñ˜€ß¼þ≯[m°B±å†+LÀ;tK ÐMÀ?ÿ»©¨#@€c+0þý›TÅÓ§O/Zh¡bÍ5×,ƒ†CQ{ë­·Š'Ÿ|² bfÿ×Xc2 ½Øb‹ å£nóú믗Að?ÿùÏŤI“Êù‘—\rÉa7ÇÉ=$ ;cÆŒòb±üòËwN‡}ð1Úᥗ^*üñâÑG-ò,bŸÑáS¦LÑ/»ì²ržéjç–Ÿyæ™"sSgnäü$ˆzX.• @€ @€ @€³C`¾ÙqRç$@€¡ Üö«? ½±– @€ 0bï_#¦³# @€À\ °< ../ require ( github.com/stretchr/testify v1.9.0 go.opentelemetry.io/contrib/instrgen v0.0.0-00010101000000-000000000000 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect golang.org/x/mod v0.20.0 // indirect golang.org/x/sync v0.8.0 // indirect golang.org/x/tools v0.24.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/driver/go.sum000066400000000000000000000025001470323427300265250ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/driver/instrgen_test.go000066400000000000000000000055201470323427300306160ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 //go:build !windows package main import ( "bytes" "fmt" "os" "path/filepath" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" alib "go.opentelemetry.io/contrib/instrgen/lib" ) var testcases = map[string]string{ "./testdata/basic": "./testdata/expected/basic", "./testdata/selector": "./testdata/expected/selector", "./testdata/interface": "./testdata/expected/interface", } var failures []string func inject(t *testing.T, root string, packagePattern string) { err := executeCommand("--inject-dump-ir", root, packagePattern) require.NoError(t, err) } func TestCommands(t *testing.T) { err := executeCommand("--dumpcfg", "./testdata/dummy", "./...") require.NoError(t, err) err = executeCommand("--rootfunctions", "./testdata/dummy", "./...") require.NoError(t, err) err = executeCommand("--prune", "./testdata/dummy", "./...") require.NoError(t, err) err = executeCommand("--inject", "./testdata/dummy", "./...") require.NoError(t, err) err = usage() require.NoError(t, err) } func TestCallGraph(t *testing.T) { cg := makeCallGraph("./testdata/dummy", "./...") dumpCallGraph(cg) assert.Empty(t, cg, "callgraph should contain 0 elems") rf := makeRootFunctions("./testdata/dummy", "./...") dumpRootFunctions(rf) assert.Empty(t, rf, "rootfunctions set should be empty") } func TestArgs(t *testing.T) { err := checkArgs(nil) require.Error(t, err) args := []string{"driver", "--inject", "", "./..."} err = checkArgs(args) require.NoError(t, err) } func TestUnknownCommand(t *testing.T) { err := executeCommand("unknown", "a", "b") require.Error(t, err) } func TestInstrumentation(t *testing.T) { for k, v := range testcases { inject(t, k, "./...") files := alib.SearchFiles(k, ".go_pass_tracing") expectedFiles := alib.SearchFiles(v, ".go") numOfFiles := len(expectedFiles) fmt.Println("Go Files:", len(files)) fmt.Println("Expected Go Files:", len(expectedFiles)) numOfComparisons := 0 for _, file := range files { fmt.Println(filepath.Base(file)) for _, expectedFile := range expectedFiles { fmt.Println(filepath.Base(expectedFile)) if filepath.Base(file) == filepath.Base(expectedFile+"_pass_tracing") { f1, err1 := os.ReadFile(file) require.NoError(t, err1) f2, err2 := os.ReadFile(expectedFile) require.NoError(t, err2) if !assert.True(t, bytes.Equal(f1, f2), file) { failures = append(failures, file) } numOfComparisons = numOfComparisons + 1 } } } if numOfFiles != numOfComparisons { fmt.Println("numberOfComparisons:", numOfComparisons) panic("not all files were compared") } _, err := Prune(k, "./...", false) if err != nil { fmt.Println("Prune failed") } } for _, f := range failures { fmt.Println("FAILURE : ", f) } } open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/driver/main.go000066400000000000000000000120601470323427300266470ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package main import ( "errors" "fmt" "go/ast" "log" "os" alib "go.opentelemetry.io/contrib/instrgen/lib" ) func usage() error { fmt.Println("\nusage driver --command [path to go project] [package pattern]") fmt.Println("\tcommand:") fmt.Println("\t\tinject (injects open telemetry calls into project code)") fmt.Println("\t\tinject-dump-ir (injects open telemetry calls into project code and intermediate passes)") fmt.Println("\t\tprune (prune open telemetry calls") fmt.Println("\t\tdumpcfg (dumps control flow graph)") fmt.Println("\t\trootfunctions (dumps root functions)") return nil } func makeAnalysis(projectPath string, packagePattern string, debug bool) *alib.PackageAnalysis { var rootFunctions []alib.FuncDescriptor interfaces := alib.FindInterfaces(projectPath, packagePattern) rootFunctions = append(rootFunctions, alib.FindRootFunctions(projectPath, packagePattern, "AutotelEntryPoint")...) funcDecls := alib.FindFuncDecls(projectPath, packagePattern, interfaces) backwardCallGraph := alib.BuildCallGraph(projectPath, packagePattern, funcDecls, interfaces) fmt.Println("\n\tchild parent") for k, v := range backwardCallGraph { fmt.Print("\n\t", k) fmt.Print(" ", v) } fmt.Println("") analysis := &alib.PackageAnalysis{ ProjectPath: projectPath, PackagePattern: packagePattern, RootFunctions: rootFunctions, FuncDecls: funcDecls, Callgraph: backwardCallGraph, Interfaces: interfaces, Debug: debug, } return analysis } // Prune. func Prune(projectPath string, packagePattern string, debug bool) ([]*ast.File, error) { analysis := makeAnalysis(projectPath, packagePattern, debug) return analysis.Execute(&alib.OtelPruner{}, otelPrunerPassSuffix) } func makeCallGraph(projectPath string, packagePattern string) map[alib.FuncDescriptor][]alib.FuncDescriptor { var funcDecls map[alib.FuncDescriptor]bool var backwardCallGraph map[alib.FuncDescriptor][]alib.FuncDescriptor interfaces := alib.FindInterfaces(projectPath, packagePattern) funcDecls = alib.FindFuncDecls(projectPath, packagePattern, interfaces) backwardCallGraph = alib.BuildCallGraph(projectPath, packagePattern, funcDecls, interfaces) return backwardCallGraph } func makeRootFunctions(projectPath string, packagePattern string) []alib.FuncDescriptor { var rootFunctions []alib.FuncDescriptor rootFunctions = append(rootFunctions, alib.FindRootFunctions(projectPath, packagePattern, "AutotelEntryPoint")...) return rootFunctions } func dumpCallGraph(callGraph map[alib.FuncDescriptor][]alib.FuncDescriptor) { fmt.Println("\n\tchild parent") for k, v := range callGraph { fmt.Print("\n\t", k) fmt.Print(" ", v) } } func dumpRootFunctions(rootFunctions []alib.FuncDescriptor) { fmt.Println("rootfunctions:") for _, fun := range rootFunctions { fmt.Println("\t" + fun.TypeHash()) } } func isDirectory(path string) (bool, error) { fileInfo, err := os.Stat(path) if err != nil { return false, err } return fileInfo.IsDir(), err } // Parsing algorithm works as follows. It goes through all function // decls and infer function bodies to find call to AutotelEntryPoint // A parent function of this call will become root of instrumentation // Each function call from this place will be instrumented automatically. func executeCommand(command string, projectPath string, packagePattern string) error { isDir, err := isDirectory(projectPath) if !isDir { _ = usage() return errors.New("[path to go project] argument must be directory") } if err != nil { return err } switch command { case "--inject": _, err := Prune(projectPath, packagePattern, false) if err != nil { return err } analysis := makeAnalysis(projectPath, packagePattern, false) err = ExecutePasses(analysis) if err != nil { return err } fmt.Println("\tinstrumentation done") return nil case "--inject-dump-ir": _, err := Prune(projectPath, packagePattern, true) if err != nil { return err } analysis := makeAnalysis(projectPath, packagePattern, true) err = ExecutePassesDumpIr(analysis) if err != nil { return err } fmt.Println("\tinstrumentation done") return nil case "--dumpcfg": backwardCallGraph := makeCallGraph(projectPath, packagePattern) dumpCallGraph(backwardCallGraph) return nil case "--rootfunctions": rootFunctions := makeRootFunctions(projectPath, packagePattern) dumpRootFunctions(rootFunctions) return nil case "--prune": _, err := Prune(projectPath, packagePattern, false) if err != nil { return err } return nil default: return errors.New("unknown command") } } func checkArgs(args []string) error { if len(args) != 4 { _ = usage() return errors.New("wrong arguments") } return nil } func main() { fmt.Println("autotel compiler") err := checkArgs(os.Args) if err != nil { return } err = executeCommand(os.Args[1], os.Args[2], os.Args[3]) if err != nil { log.Fatal(err) } } open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/driver/passes.go000066400000000000000000000017751470323427300272340ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package main import ( "fmt" "go.opentelemetry.io/contrib/instrgen/lib" ) const ( otelPrunerPassSuffix = "_pass_pruner" contextPassFileSuffix = "_pass_ctx" instrumentationPassFileSuffix = "_pass_tracing" ) // ExecutePassesDumpIr. func ExecutePassesDumpIr(analysis *lib.PackageAnalysis) error { fmt.Println("Instrumentation") _, err := analysis.Execute(&lib.InstrumentationPass{}, "") if err != nil { return err } fmt.Println("ContextPropagation") _, err = analysis.Execute(&lib.ContextPropagationPass{}, instrumentationPassFileSuffix) return err } // ExecutePasses. func ExecutePasses(analysis *lib.PackageAnalysis) error { fmt.Println("Instrumentation") _, err := analysis.Execute(&lib.InstrumentationPass{}, instrumentationPassFileSuffix) if err != nil { return err } fmt.Println("ContextPropagation") _, err = analysis.Execute(&lib.ContextPropagationPass{}, contextPassFileSuffix) return err } open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/driver/testdata/000077500000000000000000000000001470323427300272065ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/driver/testdata/basic/000077500000000000000000000000001470323427300302675ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/driver/testdata/basic/fib.go000066400000000000000000000011651470323427300313610ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 //nolint:all // Linter is executed at the same time as tests which leads to race conditions and failures. package main import ( "fmt" ) func foo() { fmt.Println("foo") } func FibonacciHelper(n uint) (uint64, error) { func() { foo() }() return Fibonacci(n) } func Fibonacci(n uint) (uint64, error) { if n <= 1 { return uint64(n), nil } if n > 93 { return 0, fmt.Errorf("unsupported fibonacci number %d: too large", n) } var n2, n1 uint64 = 0, 1 for i := uint(2); i < n; i++ { n2, n1 = n1, n1+n2 } return n2 + n1, nil } open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/driver/testdata/basic/goroutines.go000066400000000000000000000005401470323427300330130ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 //nolint:all // Linter is executed at the same time as tests which leads to race conditions and failures. package main import ( "fmt" ) func goroutines() { messages := make(chan string) go func() { messages <- "ping" }() msg := <-messages fmt.Println(msg) } open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/driver/testdata/basic/main.go000066400000000000000000000006701470323427300315450ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 //nolint:all // Linter is executed at the same time as tests which leads to race conditions and failures. package main import ( "fmt" "go.opentelemetry.io/contrib/instrgen/rtlib" ) func recur(n int) { if n > 0 { recur(n - 1) } } func main() { rtlib.AutotelEntryPoint() fmt.Println(FibonacciHelper(10)) recur(5) goroutines() pack() methods() } open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/driver/testdata/basic/methods.go000066400000000000000000000011231470323427300322560ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 //nolint:all // Linter is executed at the same time as tests which leads to race conditions and failures. package main type element struct { } type driver struct { e element } type i interface { anotherfoo(p int) int } type impl struct { } func (i impl) anotherfoo(p int) int { return 5 } func anotherfoo(p int) int { return 1 } func (d driver) process(a int) { } func (e element) get(a int) { } func methods() { d := driver{} d.process(10) d.e.get(5) var in i in = impl{} in.anotherfoo(10) } open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/driver/testdata/basic/package.go000066400000000000000000000005151470323427300322120ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 //nolint:all // Linter is executed at the same time as tests which leads to race conditions and failures. package main import ( "os" ) func Close() error { return nil } func pack() { f, e := os.Create("temp") defer f.Close() if e != nil { } } open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/driver/testdata/dummy/000077500000000000000000000000001470323427300303415ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/driver/testdata/dummy/main.go000066400000000000000000000003271470323427300316160ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 //nolint:all // Linter is executed at the same time as tests which leads to race conditions and failures. package main func main() { } open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/driver/testdata/expected/000077500000000000000000000000001470323427300310075ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/driver/testdata/expected/basic/000077500000000000000000000000001470323427300320705ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/driver/testdata/expected/basic/fib.go000066400000000000000000000030161470323427300331570ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 //nolint:all // Linter is executed at the same time as tests which leads to race conditions and failures. package main import ( "fmt" __atel_context "context" __atel_otel "go.opentelemetry.io/otel" ) func foo(__atel_tracing_ctx __atel_context.Context,) { __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("foo").Start(__atel_tracing_ctx, "foo") _ = __atel_child_tracing_ctx defer __atel_span.End() fmt.Println("foo") } func FibonacciHelper(__atel_tracing_ctx __atel_context.Context, n uint) (uint64, error) { __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("FibonacciHelper").Start(__atel_tracing_ctx, "FibonacciHelper") _ = __atel_child_tracing_ctx defer __atel_span.End() func() { __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("anonymous").Start(__atel_child_tracing_ctx, "anonymous") _ = __atel_child_tracing_ctx defer __atel_span.End() foo(__atel_child_tracing_ctx) }() return Fibonacci(__atel_child_tracing_ctx, n) } func Fibonacci(__atel_tracing_ctx __atel_context.Context, n uint) (uint64, error) { __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("Fibonacci").Start(__atel_tracing_ctx, "Fibonacci") _ = __atel_child_tracing_ctx defer __atel_span.End() if n <= 1 { return uint64(n), nil } if n > 93 { return 0, fmt.Errorf("unsupported fibonacci number %d: too large", n) } var n2, n1 uint64 = 0, 1 for i := uint(2); i < n; i++ { n2, n1 = n1, n1+n2 } return n2 + n1, nil } goroutines.go000066400000000000000000000014451470323427300345420ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/driver/testdata/expected/basic// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 //nolint:all // Linter is executed at the same time as tests which leads to race conditions and failures. package main import ( "fmt" __atel_context "context" __atel_otel "go.opentelemetry.io/otel" ) func goroutines(__atel_tracing_ctx __atel_context.Context,) { __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("goroutines").Start(__atel_tracing_ctx, "goroutines") _ = __atel_child_tracing_ctx defer __atel_span.End() messages := make(chan string) go func() { __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("anonymous").Start(__atel_child_tracing_ctx, "anonymous") _ = __atel_child_tracing_ctx defer __atel_span.End() messages <- "ping" }() msg := <-messages fmt.Println(msg) } open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/driver/testdata/expected/basic/main.go000066400000000000000000000022141470323427300333420ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 //nolint:all // Linter is executed at the same time as tests which leads to race conditions and failures. package main import ( "fmt" __atel_context "context" "go.opentelemetry.io/contrib/instrgen/rtlib" __atel_otel "go.opentelemetry.io/otel" ) func recur(__atel_tracing_ctx __atel_context.Context, n int) { __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("recur").Start(__atel_tracing_ctx, "recur") _ = __atel_child_tracing_ctx defer __atel_span.End() if n > 0 { recur(__atel_child_tracing_ctx, n-1) } } func main() { __atel_ts := rtlib.NewTracingState() defer rtlib.Shutdown(__atel_ts) __atel_otel.SetTracerProvider(__atel_ts.Tp) __atel_ctx := __atel_context.Background() __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("main").Start(__atel_ctx, "main") _ = __atel_child_tracing_ctx defer __atel_span.End() rtlib.AutotelEntryPoint() fmt.Println(FibonacciHelper(__atel_child_tracing_ctx, 10)) recur(__atel_child_tracing_ctx, 5) goroutines(__atel_child_tracing_ctx) pack(__atel_child_tracing_ctx) methods(__atel_child_tracing_ctx) } open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/driver/testdata/expected/basic/methods.go000066400000000000000000000031171470323427300340640ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 //nolint:all // Linter is executed at the same time as tests which leads to race conditions and failures. package main import ( __atel_context "context" __atel_otel "go.opentelemetry.io/otel" ) type element struct { } type driver struct { e element } type i interface { anotherfoo(__atel_tracing_ctx __atel_context.Context, p int) int } type impl struct { } func (i impl) anotherfoo(__atel_tracing_ctx __atel_context.Context, p int) int { __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("anotherfoo").Start(__atel_tracing_ctx, "anotherfoo") _ = __atel_child_tracing_ctx defer __atel_span.End() return 5 } func anotherfoo(p int) int { return 1 } func (d driver) process(__atel_tracing_ctx __atel_context.Context, a int) { __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("process").Start(__atel_tracing_ctx, "process") _ = __atel_child_tracing_ctx defer __atel_span.End() } func (e element) get(__atel_tracing_ctx __atel_context.Context, a int) { __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("get").Start(__atel_tracing_ctx, "get") _ = __atel_child_tracing_ctx defer __atel_span.End() } func methods(__atel_tracing_ctx __atel_context.Context,) { __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("methods").Start(__atel_tracing_ctx, "methods") _ = __atel_child_tracing_ctx defer __atel_span.End() d := driver{} d.process(__atel_child_tracing_ctx, 10) d.e.get(__atel_child_tracing_ctx, 5) var in i in = impl{} in.anotherfoo(__atel_child_tracing_ctx, 10) } open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/driver/testdata/expected/basic/package.go000066400000000000000000000011261470323427300340120ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 //nolint:all // Linter is executed at the same time as tests which leads to race conditions and failures. package main import ( "os" __atel_context "context" __atel_otel "go.opentelemetry.io/otel" ) func Close() error { return nil } func pack(__atel_tracing_ctx __atel_context.Context,) { __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("pack").Start(__atel_tracing_ctx, "pack") _ = __atel_child_tracing_ctx defer __atel_span.End() f, e := os.Create("temp") defer f.Close() if e != nil { } } open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/driver/testdata/expected/basic/traces.txt000066400000000000000000000056261470323427300341230ustar00rootroot00000000000000{ "Name": "foo", "SpanContext": { "TraceID": "374c11217817c32b01876bb2e2aceae8", "SpanID": "8c982b3e435c56e0", "TraceFlags": "01", "TraceState": "", "Remote": false }, "Parent": { "TraceID": "374c11217817c32b01876bb2e2aceae8", "SpanID": "65c37661e2869798", "TraceFlags": "01", "TraceState": "", "Remote": false }, "SpanKind": 1, "StartTime": "0001-01-01T00:00:00Z", "EndTime": "0001-01-01T00:00:00Z", "Attributes": null, "Events": null, "Links": null, "Status": { "Code": "Unset", "Description": "" }, "DroppedAttributes": 0, "DroppedEvents": 0, "DroppedLinks": 0, "ChildSpanCount": 0, "Resource": null, "InstrumentationLibrary": { "Name": "foo", "Version": "", "SchemaURL": "" } } { "Name": "Fibonacci", "SpanContext": { "TraceID": "374c11217817c32b01876bb2e2aceae8", "SpanID": "6676950f24fe09e2", "TraceFlags": "01", "TraceState": "", "Remote": false }, "Parent": { "TraceID": "374c11217817c32b01876bb2e2aceae8", "SpanID": "65c37661e2869798", "TraceFlags": "01", "TraceState": "", "Remote": false }, "SpanKind": 1, "StartTime": "0001-01-01T00:00:00Z", "EndTime": "0001-01-01T00:00:00Z", "Attributes": null, "Events": null, "Links": null, "Status": { "Code": "Unset", "Description": "" }, "DroppedAttributes": 0, "DroppedEvents": 0, "DroppedLinks": 0, "ChildSpanCount": 0, "Resource": null, "InstrumentationLibrary": { "Name": "Fibonacci", "Version": "", "SchemaURL": "" } } { "Name": "FibonacciHelper", "SpanContext": { "TraceID": "374c11217817c32b01876bb2e2aceae8", "SpanID": "65c37661e2869798", "TraceFlags": "01", "TraceState": "", "Remote": false }, "Parent": { "TraceID": "374c11217817c32b01876bb2e2aceae8", "SpanID": "89a2f3b8fc474d6a", "TraceFlags": "01", "TraceState": "", "Remote": false }, "SpanKind": 1, "StartTime": "0001-01-01T00:00:00Z", "EndTime": "0001-01-01T00:00:00Z", "Attributes": null, "Events": null, "Links": null, "Status": { "Code": "Unset", "Description": "" }, "DroppedAttributes": 0, "DroppedEvents": 0, "DroppedLinks": 0, "ChildSpanCount": 2, "Resource": null, "InstrumentationLibrary": { "Name": "FibonacciHelper", "Version": "", "SchemaURL": "" } } { "Name": "main", "SpanContext": { "TraceID": "374c11217817c32b01876bb2e2aceae8", "SpanID": "89a2f3b8fc474d6a", "TraceFlags": "01", "TraceState": "", "Remote": false }, "Parent": { "TraceID": "00000000000000000000000000000000", "SpanID": "0000000000000000", "TraceFlags": "00", "TraceState": "", "Remote": false }, "SpanKind": 1, "StartTime": "0001-01-01T00:00:00Z", "EndTime": "0001-01-01T00:00:00Z", "Attributes": null, "Events": null, "Links": null, "Status": { "Code": "Unset", "Description": "" }, "DroppedAttributes": 0, "DroppedEvents": 0, "DroppedLinks": 0, "ChildSpanCount": 1, "Resource": null, "InstrumentationLibrary": { "Name": "main", "Version": "", "SchemaURL": "" } } open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/driver/testdata/expected/interface/000077500000000000000000000000001470323427300327475ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/driver/testdata/expected/interface/app/000077500000000000000000000000001470323427300335275ustar00rootroot00000000000000impl.go000066400000000000000000000011221470323427300347340ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/driver/testdata/expected/interface/app// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 //nolint:all // Linter is executed at the same time as tests which leads to race conditions and failures. package app import ( "fmt" __atel_context "context" __atel_otel "go.opentelemetry.io/otel" ) type BasicSerializer struct { } func (b BasicSerializer) Serialize(__atel_tracing_ctx __atel_context.Context,) { __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("Serialize").Start(__atel_tracing_ctx, "Serialize") _ = __atel_child_tracing_ctx defer __atel_span.End() fmt.Println("Serialize") } open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/driver/testdata/expected/interface/main.go000066400000000000000000000016071470323427300342260ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 //nolint:all // Linter is executed at the same time as tests which leads to race conditions and failures. package main import ( . "go.opentelemetry.io/contrib/instrgen/testdata/interface/app" __atel_otel "go.opentelemetry.io/otel" __atel_context "context" . "go.opentelemetry.io/contrib/instrgen/testdata/interface/serializer" "go.opentelemetry.io/contrib/instrgen/rtlib" ) func main() { __atel_ts := rtlib.NewTracingState() defer rtlib.Shutdown(__atel_ts) __atel_otel.SetTracerProvider(__atel_ts.Tp) __atel_ctx := __atel_context.Background() __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("main").Start(__atel_ctx, "main") _ = __atel_child_tracing_ctx defer __atel_span.End() rtlib.AutotelEntryPoint() bs := BasicSerializer{} var s Serializer s = bs s.Serialize(__atel_child_tracing_ctx) } serializer/000077500000000000000000000000001470323427300350415ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/driver/testdata/expected/interfaceinterface.go000066400000000000000000000005031470323427300373260ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/driver/testdata/expected/interface/serializer// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 //nolint:all // Linter is executed at the same time as tests which leads to race conditions and failures. package serializer import __atel_context "context" type Serializer interface { Serialize(__atel_tracing_ctx __atel_context.Context,) } open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/driver/testdata/expected/selector/000077500000000000000000000000001470323427300326275ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/driver/testdata/expected/selector/main.go000066400000000000000000000021651470323427300341060ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 //nolint:all // Linter is executed at the same time as tests which leads to race conditions and failures. package main import ( "go.opentelemetry.io/contrib/instrgen/rtlib" __atel_otel "go.opentelemetry.io/otel" __atel_context "context" ) type Driver interface { Foo(__atel_tracing_ctx __atel_context.Context, i int) } type Impl struct { } func (impl Impl) Foo(__atel_tracing_ctx __atel_context.Context, i int) { __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("Foo").Start(__atel_tracing_ctx, "Foo") _ = __atel_child_tracing_ctx defer __atel_span.End() } func main() { __atel_ts := rtlib.NewTracingState() defer rtlib.Shutdown(__atel_ts) __atel_otel.SetTracerProvider(__atel_ts.Tp) __atel_ctx := __atel_context.Background() __atel_child_tracing_ctx, __atel_span := __atel_otel.Tracer("main").Start(__atel_ctx, "main") _ = __atel_child_tracing_ctx defer __atel_span.End() rtlib.AutotelEntryPoint() a := []Driver{ Impl{}, } var d Driver d = Impl{} d.Foo(__atel_child_tracing_ctx, 3) a[0].Foo(__atel_child_tracing_ctx, 4) } open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/driver/testdata/funwithoutpathtoroot/000077500000000000000000000000001470323427300335465ustar00rootroot00000000000000driver.go000066400000000000000000000005331470323427300353120ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/driver/testdata/funwithoutpathtoroot// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 //nolint:all // Linter is executed at the same time as tests which leads to race conditions and failures. package main import ( "go.opentelemetry.io/contrib/instrgen/rtlib" ) func bar() { } func foo() { bar() } func main() { rtlib.AutotelEntryPoint() bar() } open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/driver/testdata/interface/000077500000000000000000000000001470323427300311465ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/driver/testdata/interface/app/000077500000000000000000000000001470323427300317265ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/driver/testdata/interface/app/impl.go000066400000000000000000000004761470323427300332250ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 //nolint:all // Linter is executed at the same time as tests which leads to race conditions and failures. package app import ( "fmt" ) type BasicSerializer struct { } func (b BasicSerializer) Serialize() { fmt.Println("Serialize") } open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/driver/testdata/interface/go.mod000066400000000000000000000012351470323427300322550ustar00rootroot00000000000000module go.opentelemetry.io/contrib/instrgen/testdata/interface go 1.22 replace go.opentelemetry.io/contrib/instrgen => ../../.. require go.opentelemetry.io/contrib/instrgen v0.0.0-20221228173227-92e0588b124b require ( github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect go.opentelemetry.io/otel v1.31.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect go.opentelemetry.io/otel/sdk v1.31.0 // indirect go.opentelemetry.io/otel/trace v1.31.0 // indirect golang.org/x/sys v0.26.0 // indirect ) open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/driver/testdata/interface/go.sum000066400000000000000000000047141470323427300323070ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0 h1:UGZ1QwZWY67Z6BmckTU+9Rxn04m2bD3gD6Mk0OIOCPk= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0/go.mod h1:fcwWuDuaObkkChiDlhEpSq9+X1C0omv+s5mBtToAQ64= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/driver/testdata/interface/main.go000066400000000000000000000007701470323427300324250ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 //nolint:all // Linter is executed at the same time as tests which leads to race conditions and failures. package main import ( . "go.opentelemetry.io/contrib/instrgen/testdata/interface/app" . "go.opentelemetry.io/contrib/instrgen/testdata/interface/serializer" "go.opentelemetry.io/contrib/instrgen/rtlib" ) func main() { rtlib.AutotelEntryPoint() bs := BasicSerializer{} var s Serializer s = bs s.Serialize() } open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/driver/testdata/interface/serializer/000077500000000000000000000000001470323427300333175ustar00rootroot00000000000000interface.go000066400000000000000000000003701470323427300355270ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/driver/testdata/interface/serializer// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 //nolint:all // Linter is executed at the same time as tests which leads to race conditions and failures. package serializer type Serializer interface { Serialize() } open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/driver/testdata/selector/000077500000000000000000000000001470323427300310265ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/driver/testdata/selector/main.go000066400000000000000000000007321470323427300323030ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 //nolint:all // Linter is executed at the same time as tests which leads to race conditions and failures. package main import ( "go.opentelemetry.io/contrib/instrgen/rtlib" ) type Driver interface { Foo(i int) } type Impl struct { } func (impl Impl) Foo(i int) { } func main() { rtlib.AutotelEntryPoint() a := []Driver{ Impl{}, } var d Driver d = Impl{} d.Foo(3) a[0].Foo(4) } open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/go.mod000066400000000000000000000011201470323427300252020ustar00rootroot00000000000000module go.opentelemetry.io/contrib/instrgen go 1.22 require ( go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0 go.opentelemetry.io/otel/sdk v1.31.0 golang.org/x/tools v0.24.0 ) require ( github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect go.opentelemetry.io/otel/trace v1.31.0 // indirect golang.org/x/mod v0.20.0 // indirect golang.org/x/sync v0.8.0 // indirect golang.org/x/sys v0.26.0 // indirect ) open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/go.sum000066400000000000000000000056331470323427300252440ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0 h1:UGZ1QwZWY67Z6BmckTU+9Rxn04m2bD3gD6Mk0OIOCPk= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0/go.mod h1:fcwWuDuaObkkChiDlhEpSq9+X1C0omv+s5mBtToAQ64= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/lib/000077500000000000000000000000001470323427300246505ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/lib/analysis.go000066400000000000000000000065011470323427300270240ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package lib // import "go.opentelemetry.io/contrib/instrgen/lib" import ( "fmt" "go/ast" "go/printer" "go/token" "os" "golang.org/x/tools/go/ast/astutil" "golang.org/x/tools/go/packages" ) // PackageAnalysis analyze all package set according to passed // pattern. It requires an information about path, pattern, // root functions - entry points, function declarations, // and so on. type PackageAnalysis struct { ProjectPath string PackagePattern string RootFunctions []FuncDescriptor FuncDecls map[FuncDescriptor]bool Callgraph map[FuncDescriptor][]FuncDescriptor Interfaces map[string]bool Debug bool } type importaction int const ( // const that tells whether package should be imported. Add importaction = iota // or removed. Remove ) // Stores an information about operations on packages. // Currently packages can be imported with an aliases // or without. type Import struct { NamedPackage string Package string ImportAction importaction } // FileAnalysisPass executes an analysis for // specific file node - translation unit. type FileAnalysisPass interface { Execute(node *ast.File, analysis *PackageAnalysis, pkg *packages.Package, pkgs []*packages.Package) []Import } func createFile(name string) (*os.File, error) { var out *os.File out, err := os.Create(name) if err != nil { defer out.Close() } return out, err } func addImports(imports []Import, fset *token.FileSet, fileNode *ast.File) { for _, imp := range imports { if imp.ImportAction == Add { if len(imp.NamedPackage) > 0 { astutil.AddNamedImport(fset, fileNode, imp.NamedPackage, imp.Package) } else { astutil.AddImport(fset, fileNode, imp.Package) } } else { if len(imp.NamedPackage) > 0 { astutil.DeleteNamedImport(fset, fileNode, imp.NamedPackage, imp.Package) } else { astutil.DeleteImport(fset, fileNode, imp.Package) } } } } // Execute function, main entry point to analysis process. func (analysis *PackageAnalysis) Execute(pass FileAnalysisPass, fileSuffix string) ([]*ast.File, error) { fset := token.NewFileSet() cfg := &packages.Config{Fset: fset, Mode: LoadMode, Dir: analysis.ProjectPath} pkgs, err := packages.Load(cfg, analysis.PackagePattern) if err != nil { return nil, err } var fileNodeSet []*ast.File for _, pkg := range pkgs { fmt.Println("\t", pkg) // fileNode represents a translationUnit var fileNode *ast.File for _, fileNode = range pkg.Syntax { fmt.Println("\t\t", fset.File(fileNode.Pos()).Name()) var out *os.File out, err = createFile(fset.File(fileNode.Pos()).Name() + fileSuffix) if err != nil { return nil, err } if len(analysis.RootFunctions) == 0 { e := printer.Fprint(out, fset, fileNode) if e != nil { return nil, e } continue } imports := pass.Execute(fileNode, analysis, pkg, pkgs) addImports(imports, fset, fileNode) e := printer.Fprint(out, fset, fileNode) if e != nil { return nil, e } if !analysis.Debug { oldFileName := fset.File(fileNode.Pos()).Name() + fileSuffix newFileName := fset.File(fileNode.Pos()).Name() e = os.Rename(oldFileName, newFileName) if e != nil { return nil, e } } fileNodeSet = append(fileNodeSet, fileNode) } } return fileNodeSet, nil } open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/lib/callgraph.go000066400000000000000000000307131470323427300271400ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package lib // import "go.opentelemetry.io/contrib/instrgen/lib" import ( "fmt" "go/ast" "go/token" "go/types" "strings" "golang.org/x/tools/go/packages" ) // FuncDescriptor stores an information about // id, type and if function requires custom instrumentation. type FuncDescriptor struct { Id string DeclType string CustomInjection bool } // Function TypeHash. Each function is itentified by its // id and type. func (fd FuncDescriptor) TypeHash() string { return fd.Id + fd.DeclType } // LoadMode. Tells about needed information during analysis. const LoadMode packages.LoadMode = packages.NeedName | packages.NeedTypes | packages.NeedSyntax | packages.NeedTypesInfo | packages.NeedFiles func getPkgs(projectPath string, packagePattern string, fset *token.FileSet) ([]*packages.Package, error) { cfg := &packages.Config{Fset: fset, Mode: LoadMode, Dir: projectPath} pkgs, err := packages.Load(cfg, packagePattern) var packageSet []*packages.Package if err != nil { return nil, err } for _, pkg := range pkgs { fmt.Println("\t", pkg) packageSet = append(packageSet, pkg) } return packageSet, nil } // FindRootFunctions looks for all root functions eg. entry points. // Currently an entry point is a function that contains call of function // passed as functionLabel paramaterer. func FindRootFunctions(projectPath string, packagePattern string, functionLabel string) []FuncDescriptor { fset := token.NewFileSet() pkgs, _ := getPkgs(projectPath, packagePattern, fset) var currentFun FuncDescriptor var rootFunctions []FuncDescriptor for _, pkg := range pkgs { for _, node := range pkg.Syntax { ast.Inspect(node, func(n ast.Node) bool { switch xNode := n.(type) { case *ast.CallExpr: selector, ok := xNode.Fun.(*ast.SelectorExpr) if ok { if selector.Sel.Name == functionLabel { rootFunctions = append(rootFunctions, currentFun) } } case *ast.FuncDecl: if pkg.TypesInfo.Defs[xNode.Name] != nil { funId := pkg.TypesInfo.Defs[xNode.Name].Pkg().Path() + "." + pkg.TypesInfo.Defs[xNode.Name].Name() currentFun = FuncDescriptor{funId, pkg.TypesInfo.Defs[xNode.Name].Type().String(), false} fmt.Println("\t\t\tFuncDecl:", funId, pkg.TypesInfo.Defs[xNode.Name].Type().String()) } } return true }) } } return rootFunctions } // GetMostInnerAstIdent takes most inner identifier used for // function call. For a.b.foo(), `b` will be the most inner identifier. func GetMostInnerAstIdent(inSel *ast.SelectorExpr) *ast.Ident { var l []*ast.Ident var e ast.Expr e = inSel for e != nil { if _, ok := e.(*ast.Ident); ok { l = append(l, e.(*ast.Ident)) break } else if _, ok := e.(*ast.SelectorExpr); ok { l = append(l, e.(*ast.SelectorExpr).Sel) e = e.(*ast.SelectorExpr).X } else if _, ok := e.(*ast.CallExpr); ok { e = e.(*ast.CallExpr).Fun } else if _, ok := e.(*ast.IndexExpr); ok { e = e.(*ast.IndexExpr).X } else if _, ok := e.(*ast.UnaryExpr); ok { e = e.(*ast.UnaryExpr).X } else if _, ok := e.(*ast.ParenExpr); ok { e = e.(*ast.ParenExpr).X } else if _, ok := e.(*ast.SliceExpr); ok { e = e.(*ast.SliceExpr).X } else if _, ok := e.(*ast.IndexListExpr); ok { e = e.(*ast.IndexListExpr).X } else if _, ok := e.(*ast.StarExpr); ok { e = e.(*ast.StarExpr).X } else if _, ok := e.(*ast.TypeAssertExpr); ok { e = e.(*ast.TypeAssertExpr).X } else if _, ok := e.(*ast.CompositeLit); ok { // TODO dummy implementation if len(e.(*ast.CompositeLit).Elts) == 0 { e = e.(*ast.CompositeLit).Type } else { e = e.(*ast.CompositeLit).Elts[0] } } else if _, ok := e.(*ast.KeyValueExpr); ok { e = e.(*ast.KeyValueExpr).Value } else { // TODO this is uncaught expression panic("uncaught expression") } } if len(l) < 2 { panic("selector list should have at least 2 elems") } // caller or receiver is always // at position 1, function is at 0 return l[1] } // GetPkgPathFromRecvInterface builds package path taking // receiver interface into account. func GetPkgPathFromRecvInterface(pkg *packages.Package, pkgs []*packages.Package, funDeclNode *ast.FuncDecl, interfaces map[string]bool, ) string { var pkgPath string for _, v := range funDeclNode.Recv.List { for _, dependentpkg := range pkgs { for _, defs := range dependentpkg.TypesInfo.Defs { if defs == nil { continue } if _, ok := defs.Type().Underlying().(*types.Interface); !ok { continue } if len(v.Names) == 0 || pkg.TypesInfo.Defs[v.Names[0]] == nil { continue } funType := pkg.TypesInfo.Defs[v.Names[0]].Type() if types.Implements(funType, defs.Type().Underlying().(*types.Interface)) { interfaceExists := interfaces[defs.Type().String()] if interfaceExists { pkgPath = defs.Type().String() } break } } } } return pkgPath } // GetPkgPathFromFunctionRecv build package path taking function receiver parameters. func GetPkgPathFromFunctionRecv(pkg *packages.Package, pkgs []*packages.Package, funDeclNode *ast.FuncDecl, interfaces map[string]bool, ) string { pkgPath := GetPkgPathFromRecvInterface(pkg, pkgs, funDeclNode, interfaces) if len(pkgPath) != 0 { return pkgPath } for _, v := range funDeclNode.Recv.List { if len(v.Names) == 0 { continue } funType := pkg.TypesInfo.Defs[v.Names[0]].Type() pkgPath = funType.String() // We don't care if that's pointer, remove it from // type id if _, ok := funType.(*types.Pointer); ok { pkgPath = strings.TrimPrefix(pkgPath, "*") } // We don't care if called via index, remove it from // type id if _, ok := funType.(*types.Slice); ok { pkgPath = strings.TrimPrefix(pkgPath, "[]") } } return pkgPath } // GetSelectorPkgPath builds packages path according to selector expr. func GetSelectorPkgPath(sel *ast.SelectorExpr, pkg *packages.Package, pkgPath string) string { caller := GetMostInnerAstIdent(sel) if caller != nil && pkg.TypesInfo.Uses[caller] != nil { if !strings.Contains(pkg.TypesInfo.Uses[caller].Type().String(), "invalid") { pkgPath = pkg.TypesInfo.Uses[caller].Type().String() // We don't care if that's pointer, remove it from // type id if _, ok := pkg.TypesInfo.Uses[caller].Type().(*types.Pointer); ok { pkgPath = strings.TrimPrefix(pkgPath, "*") } // We don't care if called via index, remove it from // type id if _, ok := pkg.TypesInfo.Uses[caller].Type().(*types.Slice); ok { pkgPath = strings.TrimPrefix(pkgPath, "[]") } } } return pkgPath } // GetPkgNameFromUsesTable gets package name from uses table. func GetPkgNameFromUsesTable(pkg *packages.Package, ident *ast.Ident) string { var pkgPath string if pkg.TypesInfo.Uses[ident].Pkg() != nil { pkgPath = pkg.TypesInfo.Uses[ident].Pkg().Path() } return pkgPath } // GetPkgNameFromDefsTable gets package name from uses table. func GetPkgNameFromDefsTable(pkg *packages.Package, ident *ast.Ident) string { var pkgPath string if pkg.TypesInfo.Defs[ident] == nil { return pkgPath } if pkg.TypesInfo.Defs[ident].Pkg() != nil { pkgPath = pkg.TypesInfo.Defs[ident].Pkg().Path() } return pkgPath } // GetPkgPathForFunction builds package path, delegates work to // other helper functions defined above. func GetPkgPathForFunction(pkg *packages.Package, pkgs []*packages.Package, funDecl *ast.FuncDecl, interfaces map[string]bool, ) string { if funDecl.Recv != nil { return GetPkgPathFromFunctionRecv(pkg, pkgs, funDecl, interfaces) } return GetPkgNameFromDefsTable(pkg, funDecl.Name) } // BuildCallGraph builds an information about flow graph // in the following form child->parent. func BuildCallGraph( projectPath string, packagePattern string, funcDecls map[FuncDescriptor]bool, interfaces map[string]bool, ) map[FuncDescriptor][]FuncDescriptor { fset := token.NewFileSet() pkgs, _ := getPkgs(projectPath, packagePattern, fset) fmt.Println("BuildCallGraph") currentFun := FuncDescriptor{"nil", "", false} backwardCallGraph := make(map[FuncDescriptor][]FuncDescriptor) for _, pkg := range pkgs { fmt.Println("\t", pkg) for _, node := range pkg.Syntax { fmt.Println("\t\t", fset.File(node.Pos()).Name()) ast.Inspect(node, func(n ast.Node) bool { switch xNode := n.(type) { case *ast.CallExpr: if id, ok := xNode.Fun.(*ast.Ident); ok { pkgPath := GetPkgNameFromUsesTable(pkg, id) funId := pkgPath + "." + pkg.TypesInfo.Uses[id].Name() fmt.Println("\t\t\tFuncCall:", funId, pkg.TypesInfo.Uses[id].Type().String(), " @called : ", fset.File(node.Pos()).Name()) fun := FuncDescriptor{funId, pkg.TypesInfo.Uses[id].Type().String(), false} if !Contains(backwardCallGraph[fun], currentFun) { if funcDecls[fun] { backwardCallGraph[fun] = append(backwardCallGraph[fun], currentFun) } } } if sel, ok := xNode.Fun.(*ast.SelectorExpr); ok { if pkg.TypesInfo.Uses[sel.Sel] != nil { pkgPath := GetPkgNameFromUsesTable(pkg, sel.Sel) if sel.X != nil { pkgPath = GetSelectorPkgPath(sel, pkg, pkgPath) } funId := pkgPath + "." + pkg.TypesInfo.Uses[sel.Sel].Name() fmt.Println("\t\t\tFuncCall via selector:", funId, pkg.TypesInfo.Uses[sel.Sel].Type().String(), " @called : ", fset.File(node.Pos()).Name()) fun := FuncDescriptor{funId, pkg.TypesInfo.Uses[sel.Sel].Type().String(), false} if !Contains(backwardCallGraph[fun], currentFun) { if funcDecls[fun] { backwardCallGraph[fun] = append(backwardCallGraph[fun], currentFun) } } } } case *ast.FuncDecl: if pkg.TypesInfo.Defs[xNode.Name] != nil { pkgPath := GetPkgPathForFunction(pkg, pkgs, xNode, interfaces) funId := pkgPath + "." + pkg.TypesInfo.Defs[xNode.Name].Name() funcDecls[FuncDescriptor{funId, pkg.TypesInfo.Defs[xNode.Name].Type().String(), false}] = true currentFun = FuncDescriptor{funId, pkg.TypesInfo.Defs[xNode.Name].Type().String(), false} fmt.Println("\t\t\tFuncDecl:", funId, pkg.TypesInfo.Defs[xNode.Name].Type().String()) } } return true }) } } return backwardCallGraph } // FindFuncDecls looks for all function declarations. func FindFuncDecls(projectPath string, packagePattern string, interfaces map[string]bool) map[FuncDescriptor]bool { fset := token.NewFileSet() pkgs, _ := getPkgs(projectPath, packagePattern, fset) fmt.Println("FindFuncDecls") funcDecls := make(map[FuncDescriptor]bool) for _, pkg := range pkgs { fmt.Println("\t", pkg) for _, node := range pkg.Syntax { fmt.Println("\t\t", fset.File(node.Pos()).Name()) ast.Inspect(node, func(n ast.Node) bool { if funDeclNode, ok := n.(*ast.FuncDecl); ok { pkgPath := GetPkgPathForFunction(pkg, pkgs, funDeclNode, interfaces) if pkg.TypesInfo.Defs[funDeclNode.Name] != nil { funId := pkgPath + "." + pkg.TypesInfo.Defs[funDeclNode.Name].Name() fmt.Println("\t\t\tFuncDecl:", funId, pkg.TypesInfo.Defs[funDeclNode.Name].Type().String()) funcDecls[FuncDescriptor{funId, pkg.TypesInfo.Defs[funDeclNode.Name].Type().String(), false}] = true } } return true }) } } return funcDecls } // FindInterfaces looks for all interfaces. func FindInterfaces(projectPath string, packagePattern string) map[string]bool { fset := token.NewFileSet() pkgs, _ := getPkgs(projectPath, packagePattern, fset) fmt.Println("FindInterfaces") interaceTable := make(map[string]bool) for _, pkg := range pkgs { fmt.Println("\t", pkg) for _, node := range pkg.Syntax { fmt.Println("\t\t", fset.File(node.Pos()).Name()) ast.Inspect(node, func(n ast.Node) bool { if typeSpecNode, ok := n.(*ast.TypeSpec); ok { if _, ok := typeSpecNode.Type.(*ast.InterfaceType); ok { fmt.Println("\t\t\tInterface:", pkg.TypesInfo.Defs[typeSpecNode.Name].Type().String()) interaceTable[pkg.TypesInfo.Defs[typeSpecNode.Name].Type().String()] = true } } return true }) } } return interaceTable } // InferRootFunctionsFromGraph tries to infer entry points from passed call graph. func InferRootFunctionsFromGraph(callgraph map[FuncDescriptor][]FuncDescriptor) []FuncDescriptor { var allFunctions map[FuncDescriptor]bool var rootFunctions []FuncDescriptor allFunctions = make(map[FuncDescriptor]bool) for k, v := range callgraph { allFunctions[k] = true for _, childFun := range v { allFunctions[childFun] = true } } for k := range allFunctions { _, exists := callgraph[k] if !exists { rootFunctions = append(rootFunctions, k) } } return rootFunctions } open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/lib/context_propagation.go000066400000000000000000000125401470323427300312700ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package lib // import "go.opentelemetry.io/contrib/instrgen/lib" import ( "fmt" "go/ast" "golang.org/x/tools/go/packages" ) func isFunPartOfCallGraph(fun FuncDescriptor, callgraph map[FuncDescriptor][]FuncDescriptor) bool { // TODO this is not optimap o(n) for k, v := range callgraph { if k.TypeHash() == fun.TypeHash() { return true } for _, e := range v { if fun.TypeHash() == e.TypeHash() { return true } } } return false } // ContextPropagationPass. type ContextPropagationPass struct{} // Execute. func (pass *ContextPropagationPass) Execute( node *ast.File, analysis *PackageAnalysis, pkg *packages.Package, pkgs []*packages.Package, ) []Import { var imports []Import addImports := false // below variable is used // when callexpr is inside var decl // instead of functiondecl currentFun := FuncDescriptor{} emitEmptyContext := func(callExpr *ast.CallExpr, ctxArg *ast.Ident) { addImports = true if currentFun != (FuncDescriptor{}) { visited := map[FuncDescriptor]bool{} if isPath(analysis.Callgraph, currentFun, analysis.RootFunctions[0], visited) { callExpr.Args = append([]ast.Expr{ctxArg}, callExpr.Args...) } else { contextTodo := &ast.CallExpr{ Fun: &ast.SelectorExpr{ X: &ast.Ident{ Name: "__atel_context", }, Sel: &ast.Ident{ Name: "TODO", }, }, Lparen: 62, Ellipsis: 0, } callExpr.Args = append([]ast.Expr{contextTodo}, callExpr.Args...) } return } contextTodo := &ast.CallExpr{ Fun: &ast.SelectorExpr{ X: &ast.Ident{ Name: "__atel_context", }, Sel: &ast.Ident{ Name: "TODO", }, }, Lparen: 62, Ellipsis: 0, } callExpr.Args = append([]ast.Expr{contextTodo}, callExpr.Args...) } emitCallExpr := func(ident *ast.Ident, n ast.Node, ctxArg *ast.Ident, pkgPath string) { if callExpr, ok := n.(*ast.CallExpr); ok { funId := pkgPath + "." + pkg.TypesInfo.Uses[ident].Name() fun := FuncDescriptor{ Id: funId, DeclType: pkg.TypesInfo.Uses[ident].Type().String(), CustomInjection: false, } found := analysis.FuncDecls[fun] // inject context parameter only // to these functions for which function decl // exists if found { visited := map[FuncDescriptor]bool{} if isPath(analysis.Callgraph, fun, analysis.RootFunctions[0], visited) { fmt.Println("\t\t\tContextPropagation FuncCall:", funId, pkg.TypesInfo.Uses[ident].Type().String()) emitEmptyContext(callExpr, ctxArg) } } } } ast.Inspect(node, func(n ast.Node) bool { ctxArg := &ast.Ident{ Name: "__atel_child_tracing_ctx", } ctxField := &ast.Field{ Names: []*ast.Ident{ { Name: "__atel_tracing_ctx", }, }, Type: &ast.SelectorExpr{ X: &ast.Ident{ Name: "__atel_context", }, Sel: &ast.Ident{ Name: "Context", }, }, } switch xNode := n.(type) { case *ast.FuncDecl: pkgPath := GetPkgPathForFunction(pkg, pkgs, xNode, analysis.Interfaces) funId := pkgPath + "." + pkg.TypesInfo.Defs[xNode.Name].Name() fun := FuncDescriptor{ Id: funId, DeclType: pkg.TypesInfo.Defs[xNode.Name].Type().String(), CustomInjection: false, } currentFun = fun // inject context only // functions available in the call graph if !isFunPartOfCallGraph(fun, analysis.Callgraph) { break } if Contains(analysis.RootFunctions, fun) { break } visited := map[FuncDescriptor]bool{} if isPath(analysis.Callgraph, fun, analysis.RootFunctions[0], visited) { fmt.Println("\t\t\tContextPropagation FuncDecl:", funId, pkg.TypesInfo.Defs[xNode.Name].Type().String()) addImports = true xNode.Type.Params.List = append([]*ast.Field{ctxField}, xNode.Type.Params.List...) } case *ast.CallExpr: if ident, ok := xNode.Fun.(*ast.Ident); ok { if pkg.TypesInfo.Uses[ident] == nil { return false } pkgPath := GetPkgNameFromUsesTable(pkg, ident) emitCallExpr(ident, n, ctxArg, pkgPath) } if sel, ok := xNode.Fun.(*ast.SelectorExpr); ok { if pkg.TypesInfo.Uses[sel.Sel] == nil { return false } pkgPath := GetPkgNameFromUsesTable(pkg, sel.Sel) if sel.X != nil { pkgPath = GetSelectorPkgPath(sel, pkg, pkgPath) } emitCallExpr(sel.Sel, n, ctxArg, pkgPath) } case *ast.TypeSpec: iname := xNode.Name iface, ok := xNode.Type.(*ast.InterfaceType) if !ok { return true } for _, method := range iface.Methods.List { funcType, ok := method.Type.(*ast.FuncType) if !ok { return true } visited := map[FuncDescriptor]bool{} pkgPath := GetPkgNameFromDefsTable(pkg, method.Names[0]) funId := pkgPath + "." + iname.Name + "." + pkg.TypesInfo.Defs[method.Names[0]].Name() fun := FuncDescriptor{ Id: funId, DeclType: pkg.TypesInfo.Defs[method.Names[0]].Type().String(), CustomInjection: false, } if isPath(analysis.Callgraph, fun, analysis.RootFunctions[0], visited) { fmt.Println("\t\t\tContext Propagation InterfaceType", fun.Id, fun.DeclType) addImports = true funcType.Params.List = append([]*ast.Field{ctxField}, funcType.Params.List...) } } } return true }) if addImports { imports = append(imports, Import{"__atel_context", "context", Add}) } return imports } open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/lib/instrumentation.go000066400000000000000000000164551470323427300304550ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package lib // import "go.opentelemetry.io/contrib/instrgen/lib" import ( "fmt" "go/ast" "go/token" "golang.org/x/tools/go/packages" ) // InstrumentationPass. type InstrumentationPass struct{} func makeInitStmts(name string) []ast.Stmt { childTracingSupress := &ast.AssignStmt{ Lhs: []ast.Expr{ &ast.Ident{ Name: "_", }, }, Tok: token.ASSIGN, Rhs: []ast.Expr{ &ast.Ident{ Name: "__atel_child_tracing_ctx", }, }, } s1 := &ast.AssignStmt{ Lhs: []ast.Expr{ &ast.Ident{ Name: "__atel_ts", }, }, Tok: token.DEFINE, Rhs: []ast.Expr{ &ast.CallExpr{ Fun: &ast.SelectorExpr{ X: &ast.Ident{ Name: "rtlib", }, Sel: &ast.Ident{ Name: "NewTracingState", }, }, Lparen: 54, Ellipsis: 0, }, }, } s2 := &ast.DeferStmt{ Defer: 27, Call: &ast.CallExpr{ Fun: &ast.SelectorExpr{ X: &ast.Ident{ Name: "rtlib", }, Sel: &ast.Ident{ Name: "Shutdown", }, }, Lparen: 48, Args: []ast.Expr{ &ast.Ident{ Name: "__atel_ts", }, }, Ellipsis: 0, }, } s3 := &ast.ExprStmt{ X: &ast.CallExpr{ Fun: &ast.SelectorExpr{ X: &ast.Ident{ Name: "__atel_otel", }, Sel: &ast.Ident{ Name: "SetTracerProvider", }, }, Lparen: 49, Args: []ast.Expr{ &ast.SelectorExpr{ X: &ast.Ident{ Name: "__atel_ts", }, Sel: &ast.Ident{ Name: "Tp", }, }, }, Ellipsis: 0, }, } s4 := &ast.AssignStmt{ Lhs: []ast.Expr{ &ast.Ident{ Name: "__atel_ctx", }, }, Tok: token.DEFINE, Rhs: []ast.Expr{ &ast.CallExpr{ Fun: &ast.SelectorExpr{ X: &ast.Ident{ Name: "__atel_context", }, Sel: &ast.Ident{ Name: "Background", }, }, Lparen: 52, Ellipsis: 0, }, }, } s5 := &ast.AssignStmt{ Lhs: []ast.Expr{ &ast.Ident{ Name: "__atel_child_tracing_ctx", }, &ast.Ident{ Name: "__atel_span", }, }, Tok: token.DEFINE, Rhs: []ast.Expr{ &ast.CallExpr{ Fun: &ast.SelectorExpr{ X: &ast.CallExpr{ Fun: &ast.SelectorExpr{ X: &ast.Ident{ Name: "__atel_otel", }, Sel: &ast.Ident{ Name: "Tracer", }, }, Lparen: 50, Args: []ast.Expr{ &ast.Ident{ Name: `"` + name + `"`, }, }, Ellipsis: 0, }, Sel: &ast.Ident{ Name: "Start", }, }, Lparen: 62, Args: []ast.Expr{ &ast.Ident{ Name: "__atel_ctx", }, &ast.Ident{ Name: `"` + name + `"`, }, }, Ellipsis: 0, }, }, } s6 := &ast.DeferStmt{ Defer: 27, Call: &ast.CallExpr{ Fun: &ast.SelectorExpr{ X: &ast.Ident{ Name: "__atel_span", }, Sel: &ast.Ident{ Name: "End", }, }, Lparen: 41, Ellipsis: 0, }, } stmts := []ast.Stmt{s1, s2, s3, s4, s5, childTracingSupress, s6} return stmts } func makeSpanStmts(name string, paramName string) []ast.Stmt { s1 := &ast.AssignStmt{ Lhs: []ast.Expr{ &ast.Ident{ Name: "__atel_child_tracing_ctx", }, &ast.Ident{ Name: "__atel_span", }, }, Tok: token.DEFINE, Rhs: []ast.Expr{ &ast.CallExpr{ Fun: &ast.SelectorExpr{ X: &ast.CallExpr{ Fun: &ast.SelectorExpr{ X: &ast.Ident{ Name: "__atel_otel", }, Sel: &ast.Ident{ Name: "Tracer", }, }, Lparen: 50, Args: []ast.Expr{ &ast.Ident{ Name: `"` + name + `"`, }, }, Ellipsis: 0, }, Sel: &ast.Ident{ Name: "Start", }, }, Lparen: 62, Args: []ast.Expr{ &ast.Ident{ Name: paramName, }, &ast.Ident{ Name: `"` + name + `"`, }, }, Ellipsis: 0, }, }, } s2 := &ast.AssignStmt{ Lhs: []ast.Expr{ &ast.Ident{ Name: "_", }, }, Tok: token.ASSIGN, Rhs: []ast.Expr{ &ast.Ident{ Name: "__atel_child_tracing_ctx", }, }, } s3 := &ast.DeferStmt{ Defer: 27, Call: &ast.CallExpr{ Fun: &ast.SelectorExpr{ X: &ast.Ident{ Name: "__atel_span", }, Sel: &ast.Ident{ Name: "End", }, }, Lparen: 41, Ellipsis: 0, }, } stmts := []ast.Stmt{s1, s2, s3} return stmts } // Execute. func (pass *InstrumentationPass) Execute( node *ast.File, analysis *PackageAnalysis, pkg *packages.Package, pkgs []*packages.Package, ) []Import { var imports []Import addImports := false addContext := false // store all function literals positions // that are part of assignment statement // it's used to avoid injection into literal // more than once var functionLiteralPositions []token.Pos ast.Inspect(node, func(n ast.Node) bool { switch x := n.(type) { case *ast.FuncDecl: pkgPath := GetPkgPathForFunction(pkg, pkgs, x, analysis.Interfaces) fundId := pkgPath + "." + pkg.TypesInfo.Defs[x.Name].Name() fun := FuncDescriptor{ Id: fundId, DeclType: pkg.TypesInfo.Defs[x.Name].Type().String(), CustomInjection: false, } // check if it's root function or // one of function in call graph // and emit proper ast nodes _, exists := analysis.Callgraph[fun] if !exists { if !Contains(analysis.RootFunctions, fun) { return false } } for _, root := range analysis.RootFunctions { visited := map[FuncDescriptor]bool{} fmt.Println("\t\t\tInstrumentation FuncDecl:", fundId, pkg.TypesInfo.Defs[x.Name].Type().String()) if isPath(analysis.Callgraph, fun, root, visited) && fun.TypeHash() != root.TypeHash() { x.Body.List = append(makeSpanStmts(x.Name.Name, "__atel_tracing_ctx"), x.Body.List...) addContext = true addImports = true } else { // check whether this function is root function if !Contains(analysis.RootFunctions, fun) { return false } x.Body.List = append(makeInitStmts(x.Name.Name), x.Body.List...) addContext = true addImports = true } } case *ast.AssignStmt: for _, e := range x.Lhs { if ident, ok := e.(*ast.Ident); ok { _ = ident pkgPath := "" pkgPath = GetPkgNameFromDefsTable(pkg, ident) if pkg.TypesInfo.Defs[ident] == nil { return false } fundId := pkgPath + "." + pkg.TypesInfo.Defs[ident].Name() fun := FuncDescriptor{ Id: fundId, DeclType: pkg.TypesInfo.Defs[ident].Type().String(), CustomInjection: true, } _, exists := analysis.Callgraph[fun] if exists { return false } } } for _, e := range x.Rhs { if funLit, ok := e.(*ast.FuncLit); ok { functionLiteralPositions = append(functionLiteralPositions, funLit.Pos()) funLit.Body.List = append(makeSpanStmts("anonymous", "__atel_child_tracing_ctx"), funLit.Body.List...) addImports = true addContext = true } } case *ast.FuncLit: for _, pos := range functionLiteralPositions { if pos == x.Pos() { return false } } x.Body.List = append(makeSpanStmts("anonymous", "__atel_child_tracing_ctx"), x.Body.List...) addImports = true addContext = true } return true }) if addContext { imports = append(imports, Import{"__atel_context", "context", Add}) } if addImports { imports = append(imports, Import{"__atel_otel", "go.opentelemetry.io/otel", Add}) } return imports } open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/lib/otel_pruning.go000066400000000000000000000076501470323427300277140ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package lib // import "go.opentelemetry.io/contrib/instrgen/lib" import ( "go/ast" "strings" "golang.org/x/tools/go/packages" ) func removeStmt(slice []ast.Stmt, s int) []ast.Stmt { return append(slice[:s], slice[s+1:]...) } func removeField(slice []*ast.Field, s int) []*ast.Field { return append(slice[:s], slice[s+1:]...) } func removeExpr(slice []ast.Expr, s int) []ast.Expr { return append(slice[:s], slice[s+1:]...) } // OtelPruner. type OtelPruner struct{} func inspectFuncContent(fType *ast.FuncType, fBody *ast.BlockStmt) { for index := 0; index < len(fType.Params.List); index++ { param := fType.Params.List[index] for _, ident := range param.Names { if strings.Contains(ident.Name, "__atel_") { fType.Params.List = removeField(fType.Params.List, index) index-- } } } for index := 0; index < len(fBody.List); index++ { stmt := fBody.List[index] switch bodyStmt := stmt.(type) { case *ast.AssignStmt: if ident, ok := bodyStmt.Lhs[0].(*ast.Ident); ok { if strings.Contains(ident.Name, "__atel_") { fBody.List = removeStmt(fBody.List, index) index-- } } if ident, ok := bodyStmt.Rhs[0].(*ast.Ident); ok { if strings.Contains(ident.Name, "__atel_") { fBody.List = removeStmt(fBody.List, index) index-- } } case *ast.ExprStmt: if call, ok := bodyStmt.X.(*ast.CallExpr); ok { if sel, ok := call.Fun.(*ast.SelectorExpr); ok { if strings.Contains(sel.Sel.Name, "SetTracerProvider") { fBody.List = removeStmt(fBody.List, index) index-- } } } case *ast.DeferStmt: if sel, ok := bodyStmt.Call.Fun.(*ast.SelectorExpr); ok { if strings.Contains(sel.Sel.Name, "Shutdown") { if ident, ok := sel.X.(*ast.Ident); ok { if strings.Contains(ident.Name, "rtlib") { fBody.List = removeStmt(fBody.List, index) index-- } } } if ident, ok := sel.X.(*ast.Ident); ok { if strings.Contains(ident.Name, "__atel_") { fBody.List = removeStmt(fBody.List, index) index-- } } } } } } // Execute. func (pass *OtelPruner) Execute( node *ast.File, analysis *PackageAnalysis, pkg *packages.Package, pkgs []*packages.Package, ) []Import { var imports []Import ast.Inspect(node, func(n ast.Node) bool { switch x := n.(type) { case *ast.FuncDecl: inspectFuncContent(x.Type, x.Body) case *ast.CallExpr: for argIndex := 0; argIndex < len(x.Args); argIndex++ { if ident, ok := x.Args[argIndex].(*ast.Ident); ok { if strings.Contains(ident.Name, "__atel_") { x.Args = removeExpr(x.Args, argIndex) argIndex-- } } } for argIndex := 0; argIndex < len(x.Args); argIndex++ { if c, ok := x.Args[argIndex].(*ast.CallExpr); ok { if sel, ok := c.Fun.(*ast.SelectorExpr); ok { if ident, ok := sel.X.(*ast.Ident); ok { if strings.Contains(ident.Name, "__atel_") { x.Args = removeExpr(x.Args, argIndex) argIndex-- } } } } } case *ast.FuncLit: inspectFuncContent(x.Type, x.Body) case *ast.TypeSpec: iface, ok := x.Type.(*ast.InterfaceType) if !ok { return true } for _, method := range iface.Methods.List { funcType, ok := method.Type.(*ast.FuncType) if !ok { continue } for argIndex := 0; argIndex < len(funcType.Params.List); argIndex++ { for _, ident := range funcType.Params.List[argIndex].Names { if strings.Contains(ident.Name, "__atel_") { funcType.Params.List = removeField(funcType.Params.List, argIndex) argIndex-- } } } } } return true }) imports = append(imports, Import{"__atel_context", "context", Remove}) imports = append(imports, Import{"__atel_otel", "go.opentelemetry.io/otel", Remove}) imports = append(imports, Import{"__atel_otelhttp", "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp", Remove}) return imports } open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/lib/tools.go000066400000000000000000000021141470323427300263350ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package lib // import "go.opentelemetry.io/contrib/instrgen/lib" import ( "os" "path/filepath" ) // SearchFiles. func SearchFiles(root string, ext string) []string { var files []string err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { if filepath.Ext(path) == ext { files = append(files, path) } return nil }) if err != nil { panic(err) } return files } func isPath( callGraph map[FuncDescriptor][]FuncDescriptor, current FuncDescriptor, goal FuncDescriptor, visited map[FuncDescriptor]bool, ) bool { if current == goal { return true } value, ok := callGraph[current] if ok { for _, child := range value { exists := visited[child] if exists { continue } visited[child] = true if isPath(callGraph, child, goal, visited) { return true } } } return false } // Contains. func Contains(a []FuncDescriptor, x FuncDescriptor) bool { for _, n := range a { if x.TypeHash() == n.TypeHash() { return true } } return false } open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/rtlib/000077500000000000000000000000001470323427300252165ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrgen/rtlib/rtlib.go000066400000000000000000000034031470323427300266610ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Basic runtime library package rtlib // import "go.opentelemetry.io/contrib/instrgen/rtlib" import ( "context" "io" "log" "os" "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/sdk/resource" trace "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.4.0" ) // TracingState type. type TracingState struct { Logger *log.Logger File *os.File Tp *trace.TracerProvider } // NewTracingState. func NewTracingState() TracingState { var tracingState TracingState tracingState.Logger = log.New(os.Stdout, "", 0) // Write telemetry data to a file. var err error tracingState.File, err = os.Create("traces.txt") if err != nil { tracingState.Logger.Fatal(err) } var exp trace.SpanExporter exp, err = NewExporter(tracingState.File) if err != nil { tracingState.Logger.Fatal(err) } tracingState.Tp = trace.NewTracerProvider( trace.WithBatcher(exp), trace.WithResource(NewResource()), ) return tracingState } // NewExporter returns a console exporter. func NewExporter(w io.Writer) (trace.SpanExporter, error) { return stdouttrace.New( stdouttrace.WithWriter(w), // Use human readable output. stdouttrace.WithPrettyPrint(), // Do not print timestamps for the demo. stdouttrace.WithoutTimestamps(), ) } // NewResource returns a resource describing this application. func NewResource() *resource.Resource { r, _ := resource.Merge( resource.Default(), resource.NewWithAttributes( semconv.SchemaURL, ), ) return r } // Shutdown. func Shutdown(ts TracingState) { if err := ts.Tp.Shutdown(context.Background()); err != nil { ts.Logger.Fatal(err) } } // AutoEntryPoint. func AutotelEntryPoint() { } open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/000077500000000000000000000000001470323427300255145ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/README.md000066400000000000000000000137641470323427300270060ustar00rootroot00000000000000# Instrumentation Code contained in this directory contains instrumentation for 3rd-party Go packages and some packages from the standard library. ## New Instrumentation **Do not submit pull requests for new instrumentation without reading the following.** This project is dedicated to promoting the development of quality instrumentation using OpenTelemetry. To achieve this goal, we recognize that the instrumentation needs to be written using the best practices of OpenTelemetry, but also by developers that understand the package they are instrumenting. Additionally, the produced instrumentation needs to be maintained and evolved. The size of the OpenTelemetry Go developer community is not large enough to support an ever growing amount of instrumentation. Therefore, to reach our goal, we have the following recommendations for where instrumentation packages should live. 1. Native to the instrumented package 2. A dedicated public repository 3. Here in the opentelemetry-go-contrib repository If possible, OpenTelemetry instrumentation should be included in the instrumented package. This will ensure the instrumentation reaches all package users, and is continuously maintained by developers that understand the package. If instrumentation cannot be directly included in the package it is instrumenting, it should be hosted in a dedicated public repository owned by its maintainer(s). This will appropriately assign maintenance responsibilities for the instrumentation and ensure these maintainers have the needed privilege to maintain the code. The last place instrumentation should be hosted is here in this repository. Maintaining instrumentation here hampers the development of OpenTelemetry for Go and therefore should be avoided. When instrumentation cannot be included in a target package and there is good reason to not host it in a separate and dedicated repository an [instrumentation request](https://github.com/open-telemetry/opentelemetry-go-contrib/issues/new/choose) should be filed. The instrumentation request needs to be accepted before any pull requests for the instrumentation can be considered for merging. Regardless of where instrumentation is hosted, it needs to be discoverable. The [OpenTelemetry registry](https://opentelemetry.io/registry/) exists to ensure that instrumentation is discoverable. You can find out how to add instrumentation to the registry [here](https://github.com/open-telemetry/opentelemetry.io#adding-a-project-to-the-opentelemetry-registry). ## Instrumentation Packages The [OpenTelemetry registry](https://opentelemetry.io/registry/) is the best place to discover instrumentation packages. It will include packages outside of this project. The following instrumentation packages are provided for popular Go packages and use-cases. | Instrumentation Package | Metrics | Traces | | :---------------------: | :-----: | :----: | | [github.com/aws/aws-sdk-go-v2](./github.com/aws/aws-sdk-go-v2/otelaws)| | ✓ | | [github.com/emicklei/go-restful](./github.com/emicklei/go-restful/otelrestful) | | ✓ | | [github.com/gin-gonic/gin](./github.com/gin-gonic/gin/otelgin) | | ✓ | | [github.com/gorilla/mux](./github.com/gorilla/mux/otelmux) | | ✓ | | [github.com/labstack/echo](./github.com/labstack/echo/otelecho) | | ✓ | | [go.mongodb.org/mongo-driver](./go.mongodb.org/mongo-driver/mongo/otelmongo) | | ✓ | | [google.golang.org/grpc](./google.golang.org/grpc/otelgrpc) | ✓ | ✓ | | [host](./host) | ✓ | | | [net/http](./net/http/otelhttp) | ✓ | ✓ | | [net/http/httptrace](./net/http/httptrace/otelhttptrace) | | ✓ | | [runtime](./runtime) | ✓ | | ## Organization In order to ensure the maintainability and discoverability of instrumentation packages, the following guidelines MUST be followed. ### Packaging All instrumentation packages SHOULD be of the form: ```sh go.opentelemetry.io/contrib/instrumentation/{IMPORT_PATH}/otel{PACKAGE_NAME} ``` Where the [`{IMPORT_PATH}`](https://golang.org/ref/spec#ImportPath) and [`{PACKAGE_NAME}`](https://golang.org/ref/spec#PackageName) are the standard Go identifiers for the package being instrumented. For example: - `go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux` - `go.opentelemetry.io/contrib/instrumentation/gopkg.in/macaron.v1/otelmacaron` - `go.opentelemetry.io/contrib/instrumentation/database/sql/otelsql` Exceptions to this rule exist. For example, the [runtime](./runtime) and [host](./host) instrumentation do not instrument any Go package and therefore do not fit this structure. ### Contents All instrumentation packages MUST adhere to [the projects' contributing guidelines](../CONTRIBUTING.md). Additionally the following guidelines for package composition need to be followed. - All instrumentation packages MUST be a Go module. Therefore, an appropriately configured `go.mod` and `go.sum` need to exist for each package. - To help understand the instrumentation a Go package documentation SHOULD be included. This documentation SHOULD be in a dedicated `doc.go` file if the package is more than one file. It SHOULD contain useful information like what the purpose of the instrumentation is, how to use it, and any compatibility restrictions that might exist. - Examples of how to actually use the instrumentation SHOULD be included. - All instrumentation packages MUST provide an option to accept a `TracerProvider` if it uses a Tracer, a `MeterProvider` if it uses a Meter, and `Propagators` if it handles any context propagation. Also, packages MUST use the default `TracerProvider`, `MeterProvider`, and `Propagators` supplied by the `global` package if no optional one is provided. - All instrumentation packages MUST NOT provide an option to accept a `Tracer` or `Meter`. - All instrumentation packages MUST define a `ScopeName` constant with a value matching the instrumentation package and use it when creating a `Tracer` or `Meter`. - All instrumentation packages MUST define a `Version` function returning the version of the module containing the instrumentation and use it when creating a `Tracer` or `Meter`. open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/000077500000000000000000000000001470323427300275535ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/000077500000000000000000000000001470323427300303455ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-lambda-go/000077500000000000000000000000001470323427300327605ustar00rootroot00000000000000otellambda/000077500000000000000000000000001470323427300350055ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-lambda-goREADME.md000066400000000000000000000113771470323427300362750ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-lambda-go/otellambda# OpenTelemetry AWS Lambda Instrumentation for Golang [![Go Reference][goref-image]][goref-url] [![Apache License][license-image]][license-url] This module provides instrumentation for [`AWS Lambda`](https://docs.aws.amazon.com/lambda/latest/dg/golang-handler.html). ## Installation ```bash go get -u go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda ``` ## example See [./example](https://github.com/open-telemetry/opentelemetry-go-contrib/tree/main/instrumentation/github.com/aws/aws-lambda-go/otellambda/example) ## Usage Create a sample Lambda Go application such as below. ```go package main import ( "context" "fmt" "github.com/aws/aws-lambda-go/lambda" ) type MyEvent struct { Name string `json:"name"` } func HandleRequest(ctx context.Context, name MyEvent) (string, error) { return fmt.Sprintf("Hello %s!", name.Name ), nil } func main() { lambda.Start(HandleRequest) } ``` Now use the provided wrapper to instrument your basic Lambda function: ```go // Add import import "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda" // wrap lambda handler function func main() { lambda.Start(otellambda.InstrumentHandler(HandleRequest)) } ``` ## AWS Lambda Instrumentation Options | Options | Input Type | Description | Default | | --- | --- | --- | --- | | `WithTracerProvider` | `trace.TracerProvider` | Provide a custom `TracerProvider` for creating spans. Consider using the [AWS Lambda Resource Detector][lambda-detector-url] with your tracer provider to improve tracing information. | `otel.GetTracerProvider()` | `WithFlusher` | `otellambda.Flusher` | This instrumentation will call the `ForceFlush` method of its `Flusher` at the end of each invocation. Should you be using asynchronous logic (such as `sddktrace's BatchSpanProcessor`) it is very import for spans to be `ForceFlush`'ed before [Lambda freezes](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-context.html) to avoid data delays. | `Flusher` with noop `ForceFlush` | `WithEventToCarrier` | `func(eventJSON []byte) propagation.TextMapCarrier{}` | Function for providing custom logic to support retrieving trace header from different event types that are handled by AWS Lambda (e.g., SQS, CloudWatch, Kinesis, API Gateway) and returning them in a `propagation.TextMapCarrier` which a Propagator can use to extract the trace header into the context. | Function which returns an empty `TextMapCarrier` - new spans will be part of a new Trace and have no parent past Lambda instrumentation span | `WithPropagator` | `propagation.Propagator` | The `Propagator` the instrumentation will use to extract trace information into the context. | `otel.GetTextMapPropagator()` | ### Usage With Options Example ```go var someHeaderKey = "Key" // used by propagator and EventToCarrier function to identify trace header type mockHTTPRequest struct { Headers map[string][]string Body string } func mockEventToCarrier(eventJSON []byte) propagation.TextMapCarrier{ var request mockHTTPRequest _ = json.unmarshal(eventJSON, &request) return propagation.HeaderCarrier{someHeaderKey: []string{request.Headers[someHeaderKey]}} } type mockPropagator struct{} // Extract - read from `someHeaderKey` // Inject // Fields func HandleRequest(ctx context.Context, request mockHTTPRequest) error { return fmt.Sprintf("Hello %s!", request.Body ), nil } func main() { exp, _ := stdouttrace.New() tp := sdktrace.NewTracerProvider( sdktrace.WithBatcher(exp)) lambda.Start(otellambda.InstrumentHandler(HandleRequest, otellambda.WithTracerProvider(tp), otellambda.WithFlusher(tp), otellambda.WithEventToCarrier(mockEventToCarrier), otellambda.WithPropagator(mockPropagator{}))) } ``` ## Useful links - For more information on OpenTelemetry, visit: - For more about OpenTelemetry Go: - For help or feedback on this project, join us in [GitHub Discussions][discussions-url] ## License Apache 2.0 - See [LICENSE][license-url] for more information. [license-url]: https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/LICENSE [license-image]: https://img.shields.io/badge/license-Apache_2.0-green.svg?style=flat [goref-image]: https://pkg.go.dev/badge/go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda.svg [goref-url]: https://pkg.go.dev/go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda [discussions-url]: https://github.com/open-telemetry/opentelemetry-go/discussions [lambda-detector-url]: https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/detectors/aws/lambda config.go000066400000000000000000000077671470323427300366220ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-lambda-go/otellambda// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otellambda // import "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda" import ( "context" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" ) // A Flusher dictates how the instrumentation will attempt to flush // unexported spans at the end of each Lambda innovation. This is // very important in asynchronous settings because the Lambda runtime // may enter a 'frozen' state any time after the invocation completes. // Should this freeze happen and spans are left unexported, there can be a // long delay before those spans are exported. type Flusher interface { ForceFlush(context.Context) error } type noopFlusher struct{} func (*noopFlusher) ForceFlush(context.Context) error { return nil } // Compile time check our noopFlusher implements Flusher. var _ Flusher = &noopFlusher{} // An EventToCarrier function defines how the instrumentation should // prepare a TextMapCarrier for the configured propagator to read from. This // extra step is necessary because Lambda does not have HTTP headers to read // from and instead stores the headers it was invoked with (including TraceID, etc.) // as part of the invocation event. If using the AWS XRay tracing then the // trace information is instead stored in the Lambda environment. type EventToCarrier func(eventJSON []byte) propagation.TextMapCarrier func emptyEventToCarrier([]byte) propagation.TextMapCarrier { return propagation.HeaderCarrier{} } // Compile time check our emptyEventToCarrier implements EventToCarrier. var _ EventToCarrier = emptyEventToCarrier // Option applies a configuration option. type Option interface { apply(*config) } type optionFunc func(*config) func (o optionFunc) apply(c *config) { o(c) } type config struct { // TracerProvider is the TracerProvider which will be used // to create instrumentation spans // The default value of TracerProvider the global otel TracerProvider // returned by otel.GetTracerProvider() TracerProvider trace.TracerProvider // Flusher is the mechanism used to flush any unexported spans // each Lambda Invocation to avoid spans being unexported for long // when periods of time if Lambda freezes the execution environment // The default value of Flusher is a noop Flusher, using this // default can result in long data delays in asynchronous settings Flusher Flusher // EventToCarrier is the mechanism used to retrieve the TraceID // from the event or environment and generate a TextMapCarrier which // can then be used by a Propagator to extract the TraceID into our context // The default value of eventToCarrier is emptyEventToCarrier which returns // an empty HeaderCarrier, using this default will cause new spans to be part // of a new Trace and have no parent past our Lambda instrumentation span EventToCarrier EventToCarrier // Propagator is the Propagator which will be used // to extract Trace info into the context // The default value of Propagator the global otel Propagator // returned by otel.GetTextMapPropagator() Propagator propagation.TextMapPropagator } // WithTracerProvider configures the TracerProvider used by the // instrumentation. // // By default, the global TracerProvider is used. func WithTracerProvider(tracerProvider trace.TracerProvider) Option { return optionFunc(func(c *config) { c.TracerProvider = tracerProvider }) } // WithFlusher sets the used flusher. func WithFlusher(flusher Flusher) Option { return optionFunc(func(c *config) { c.Flusher = flusher }) } // WithEventToCarrier sets the used EventToCarrier. func WithEventToCarrier(eventToCarrier EventToCarrier) Option { return optionFunc(func(c *config) { c.EventToCarrier = eventToCarrier }) } // WithPropagator configures the propagator used by the instrumentation. // // By default, the global TextMapPropagator will be used. func WithPropagator(propagator propagation.TextMapPropagator) Option { return optionFunc(func(c *config) { c.Propagator = propagator }) } doc.go000066400000000000000000000011621470323427300361010ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-lambda-go/otellambda// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package otellambda instruments the github.com/aws/aws-lambda-go package. // // Two wrappers are provided which can be used to instrument Lambda, // one for each Lambda entrypoint. Their usages are shown below. // // lambda.Start() entrypoint: lambda.Start(otellambda.InstrumentHandler()) // lambda.StartHandler() entrypoint: lambda.StartHandler(otellambda.WrapHandler()) package otellambda // import "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda" example/000077500000000000000000000000001470323427300364405ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-lambda-go/otellambdaDockerfile000066400000000000000000000007441470323427300404370ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-lambda-go/otellambda/example# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 FROM golang:1.23 AS base COPY . /src/ WORKDIR /src/instrumentation/github.com/aws/aws-lambda-go/otellambda/example RUN apt-get update FROM base AS aws-lambda # install other package(s) in base RUN apt-get install zip unzip RUN curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" \ && unzip awscliv2.zip \ && ./aws/install RUN apt-get -y install jq CMD ["./build.sh"] README.md000066400000000000000000000025511470323427300377220ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-lambda-go/otellambda/example# aws/aws-lambda-go instrumentation example A simple example to demonstrate the AWS Lambda for Go instrumentation. In this example, container `aws-lambda-client` initializes an S3 client and an HTTP client and runs 2 basic operations: `listS3Buckets` and `GET`. These instructions assume you have [docker-compose](https://docs.docker.com/compose/) installed and setup, and [AWS credential](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html) configured. 1. From within the `example` directory, bring up the project by running: ```sh docker-compose up --detach ``` 2. The instrumentation works with a `stdout` exporter. The example pulls this output from AWS and outputs back to stdout. To inspect the output (following build output), you can run: ```sh docker-compose logs ``` 3. After inspecting the client logs, the example can be cleaned up by running: ```sh docker-compose down ``` Note: Because the example runs on AWS Lambda, a handful of resources are created in AWS by the example. The example will automatically destroy any resources it makes; however, if you terminate the container before it completes you may have leftover resources in AWS. Should you terminate the container early, run the below command to ensure all AWS resources are cleaned up: ```sh ./manualAWSCleanup.sh ```assumeRolePolicyDocument.json000066400000000000000000000002631470323427300443320ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-lambda-go/otellambda/example{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "lambda.amazonaws.com" }, "Action": "sts:AssumeRole" } ] }build.sh000077500000000000000000000104341470323427300401000ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-lambda-go/otellambda/example#!/bin/sh # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 # constants LAMBDA_FUNCTION_NAME=SampleLambdaGo ROLE_NAME="$LAMBDA_FUNCTION_NAME"Role POLICY_NAME="$LAMBDA_FUNCTION_NAME"Policy LOG_GROUP_NAME=/aws/lambda/"$LAMBDA_FUNCTION_NAME" AWS_ACCT_ID=$(aws sts get-caller-identity | jq '.Account | tonumber') MAX_CREATE_TRIES=5 MAX_GET_LOG_STREAM_TRIES=10 # build go executable echo "1/6 Building go executable" GOOS=linux GOARCH=amd64 go build -o ./build/bootstrap . > /dev/null cd build || exit zip bootstrap.zip bootstrap > /dev/null # create AWS resources echo "2/6 Creating necessary resources in AWS" aws iam create-role --role-name "$ROLE_NAME" --assume-role-policy-document file://../assumeRolePolicyDocument.json > /dev/null aws iam create-policy --policy-name "$POLICY_NAME" --policy-document file://../policyForRoleDocument.json > /dev/null aws iam attach-role-policy --role-name "$ROLE_NAME" --policy-arn arn:aws:iam::"$AWS_ACCT_ID":policy/"$POLICY_NAME" > /dev/null aws logs create-log-group --log-group-name "$LOG_GROUP_NAME" > /dev/null # race condition exists such that a role can be created and validated # via IAM, yet still cannot be assumed by Lambda, we will retry up to # MAX_CREATE_TRIES times to create the function TIMEOUT="$MAX_CREATE_TRIES" CREATE_FUNCTION_SUCCESS=$(aws lambda create-function --function-name "$LAMBDA_FUNCTION_NAME" --runtime provided.al2 --handler bootstrap --zip-file fileb://bootstrap.zip --role arn:aws:iam::"$AWS_ACCT_ID":role/"$ROLE_NAME" --timeout 5 --tracing-config Mode=Active > /dev/null || echo "false") while [ "$CREATE_FUNCTION_SUCCESS" = "false" ] && [ "$TIMEOUT" -ne 1 ] ; do echo " Retrying create-function, role likely not ready for use..." sleep 1 TIMEOUT=$((TIMEOUT - 1)) CREATE_FUNCTION_SUCCESS=$(aws lambda create-function --function-name "$LAMBDA_FUNCTION_NAME" --runtime provided.al2 --handler bootstrap --zip-file fileb://bootstrap.zip --role arn:aws:iam::"$AWS_ACCT_ID":role/"$ROLE_NAME" --timeout 5 --tracing-config Mode=Active > /dev/null || echo "false") done if [ "$TIMEOUT" -eq 1 ] ; then echo "Error: max retries reached when attempting to create Lambda Function" fi # invoke lambda echo "3/6 Invoking lambda" aws lambda invoke --function-name "$LAMBDA_FUNCTION_NAME" --payload "" resp.json # get logs from lambda (via cloudwatch) # logs sent from lambda to Cloudwatch and retrieved # from there because example logs are too long to # return directly from lambda invocation echo "4/6 Storing logs from AWS" # significant (3+ second) delay can occur between invoking Lambda and # the related log stream existing in Cloudwatch. We will retry to # retrieve the log stream up to MAX_GET_LOG_STREAM_TRIES TIMEOUT="$MAX_GET_LOG_STREAM_TRIES" LOG_STREAM_NAME=$(aws logs describe-log-streams --log-group-name "$LOG_GROUP_NAME" --order-by LastEventTime --descending | jq --raw-output '.logStreams[0].logStreamName') while [ "$LOG_STREAM_NAME" = "null" ] && [ "$TIMEOUT" -ne 1 ] ; do echo " Waiting for log stream to be created..." sleep 1 TIMEOUT=$((TIMEOUT - 1)) LOG_STREAM_NAME=$(aws logs describe-log-streams --log-group-name "$LOG_GROUP_NAME" --order-by LastEventTime --descending | jq --raw-output '.logStreams[0].logStreamName') done if [ "$TIMEOUT" -eq 1 ] ; then echo "Timed out waiting for log stream to be created" fi # minor (<1 second) delay can exist when adding logs to the # log stream such that only partial logs will be returned. # Will wait small amount of time to let logs fully populate sleep 2 aws logs get-log-events --log-group-name "$LOG_GROUP_NAME" --log-stream-name "$LOG_STREAM_NAME" | jq --join-output '.events[] | select(has("message")) | .message' | jq -R -r '. as $line | try fromjson catch $line' > lambdaLogs # destroy lambda resources echo "5/6 Destroying AWS resources" aws logs delete-log-stream --log-group-name "$LOG_GROUP_NAME" --log-stream-name "$LOG_STREAM_NAME" aws logs delete-log-group --log-group-name "$LOG_GROUP_NAME" aws lambda delete-function --function-name $LAMBDA_FUNCTION_NAME aws iam detach-role-policy --role-name "$ROLE_NAME" --policy-arn arn:aws:iam::"$AWS_ACCT_ID":policy/"$POLICY_NAME" aws iam delete-policy --policy-arn arn:aws:iam::"$AWS_ACCT_ID":policy/"$POLICY_NAME" aws iam delete-role --role-name "$ROLE_NAME" # display logs printf "6/6 Displaying logs from AWS:\n\n\n" cat lambdaLogs docker-compose.yml000066400000000000000000000006001470323427300420710ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-lambda-go/otellambda/example# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 version: "3.7" services: aws-lambda-client: build: dockerfile: $PWD/Dockerfile context: ../../../../../.. ports: - "8080:80" command: - "/bin/sh" - "-c" - "./build.sh" volumes: - ~/.aws:/root/.aws networks: - example networks: example: go.mod000066400000000000000000000053501470323427300375510ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-lambda-go/otellambda/examplemodule go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda/example go 1.22 replace ( go.opentelemetry.io/contrib/detectors/aws/lambda => ../../../../../../detectors/aws/lambda go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda => ../ go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws => ../../../aws-sdk-go-v2/otelaws go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp => ../../../../../net/http/otelhttp ) require ( github.com/aws/aws-lambda-go v1.47.0 github.com/aws/aws-sdk-go-v2/config v1.27.43 github.com/aws/aws-sdk-go-v2/service/s3 v1.65.3 go.opentelemetry.io/contrib/detectors/aws/lambda v0.56.0 go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda v0.56.0 go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws v0.56.0 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0 go.opentelemetry.io/otel/sdk v1.31.0 ) require ( github.com/aws/aws-sdk-go-v2 v1.32.2 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.6 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.17.41 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.17 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.21 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.21 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.21 // indirect github.com/aws/aws-sdk-go-v2/service/dynamodb v1.36.2 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0 // indirect github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.2 // indirect github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.2 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.2 // indirect github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.2 // indirect github.com/aws/aws-sdk-go-v2/service/sqs v1.36.2 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.24.2 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.2 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.32.2 // indirect github.com/aws/smithy-go v1.22.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect go.opentelemetry.io/otel/trace v1.31.0 // indirect golang.org/x/sys v0.26.0 // indirect ) go.sum000066400000000000000000000177001470323427300376000ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-lambda-go/otellambda/examplegithub.com/aws/aws-lambda-go v1.47.0 h1:0H8s0vumYx/YKs4sE7YM0ktwL2eWse+kfopsRI1sXVI= github.com/aws/aws-lambda-go v1.47.0/go.mod h1:dpMpZgvWx5vuQJfBt0zqBha60q7Dd7RfgJv23DymV8A= github.com/aws/aws-sdk-go-v2 v1.32.2 h1:AkNLZEyYMLnx/Q/mSKkcMqwNFXMAvFto9bNsHqcTduI= github.com/aws/aws-sdk-go-v2 v1.32.2/go.mod h1:2SK5n0a2karNTv5tbP1SjsX0uhttou00v/HpXKM1ZUo= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.6 h1:pT3hpW0cOHRJx8Y0DfJUEQuqPild8jRGmSFmBgvydr0= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.6/go.mod h1:j/I2++U0xX+cr44QjHay4Cvxj6FUbnxrgmqN3H1jTZA= github.com/aws/aws-sdk-go-v2/config v1.27.43 h1:p33fDDihFC390dhhuv8nOmX419wjOSDQRb+USt20RrU= github.com/aws/aws-sdk-go-v2/config v1.27.43/go.mod h1:pYhbtvg1siOOg8h5an77rXle9tVG8T+BWLWAo7cOukc= github.com/aws/aws-sdk-go-v2/credentials v1.17.41 h1:7gXo+Axmp+R4Z+AK8YFQO0ZV3L0gizGINCOWxSLY9W8= github.com/aws/aws-sdk-go-v2/credentials v1.17.41/go.mod h1:u4Eb8d3394YLubphT4jLEwN1rLNq2wFOlT6OuxFwPzU= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.17 h1:TMH3f/SCAWdNtXXVPPu5D6wrr4G5hI1rAxbcocKfC7Q= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.17/go.mod h1:1ZRXLdTpzdJb9fwTMXiLipENRxkGMTn1sfKexGllQCw= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.21 h1:UAsR3xA31QGf79WzpG/ixT9FZvQlh5HY1NRqSHBNOCk= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.21/go.mod h1:JNr43NFf5L9YaG3eKTm7HQzls9J+A9YYcGI5Quh1r2Y= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.21 h1:6jZVETqmYCadGFvrYEQfC5fAQmlo80CeL5psbno6r0s= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.21/go.mod h1:1SR0GbLlnN3QUmYaflZNiH1ql+1qrSiB2vwcJ+4UM60= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.21 h1:7edmS3VOBDhK00b/MwGtGglCm7hhwNYnjJs/PgFdMQE= github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.21/go.mod h1:Q9o5h4HoIWG8XfzxqiuK/CGUbepCJ8uTlaE3bAbxytQ= github.com/aws/aws-sdk-go-v2/service/dynamodb v1.36.2 h1:kJqyYcGqhWFmXqjRrtFFD4Oc9FXiskhsll2xnlpe8Do= github.com/aws/aws-sdk-go-v2/service/dynamodb v1.36.2/go.mod h1:+t2Zc5VNOzhaWzpGE+cEYZADsgAAQT5v55AO+fhU+2s= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0 h1:TToQNkvGguu209puTojY/ozlqy2d/SFNcoLIqTFi42g= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0/go.mod h1:0jp+ltwkf+SwG2fm/PKo8t4y8pJSgOCO4D8Lz3k0aHQ= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.2 h1:4FMHqLfk0efmTqhXVRL5xYRqlEBNBiRI7N6w4jsEdd4= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.2/go.mod h1:LWoqeWlK9OZeJxsROW2RqrSPvQHKTpp69r/iDjwsSaw= github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.2 h1:1G7TTQNPNv5fhCyIQGYk8FOggLgkzKq6c4Y1nOGzAOE= github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.2/go.mod h1:+ybYGLXoF7bcD7wIcMcklxyABZQmuBf1cHUhvY6FGIo= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.2 h1:s7NA1SOw8q/5c0wr8477yOPp0z+uBaXBnLE0XYb0POA= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.2/go.mod h1:fnjjWyAW/Pj5HYOxl9LJqWtEwS7W2qgcRLWP+uWbss0= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.2 h1:t7iUP9+4wdc5lt3E41huP+GvQZJD38WLsgVp4iOtAjg= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.2/go.mod h1:/niFCtmuQNxqx9v8WAPq5qh7EH25U4BF6tjoyq9bObM= github.com/aws/aws-sdk-go-v2/service/s3 v1.65.3 h1:xxHGZ+wUgZNACQmxtdvP5tgzfsxGS3vPpTP5Hy3iToE= github.com/aws/aws-sdk-go-v2/service/s3 v1.65.3/go.mod h1:cB6oAuus7YXRZhWCc1wIwPywwZ1XwweNp2TVAEGYeB8= github.com/aws/aws-sdk-go-v2/service/sqs v1.36.2 h1:kmbcoWgbzfh5a6rvfjOnfHSGEqD13qu1GfTPRZqg0FI= github.com/aws/aws-sdk-go-v2/service/sqs v1.36.2/go.mod h1:/UPx74a3M0WYeT2yLQYG/qHhkPlPXd6TsppfGgy2COk= github.com/aws/aws-sdk-go-v2/service/sso v1.24.2 h1:bSYXVyUzoTHoKalBmwaZxs97HU9DWWI3ehHSAMa7xOk= github.com/aws/aws-sdk-go-v2/service/sso v1.24.2/go.mod h1:skMqY7JElusiOUjMJMOv1jJsP7YUg7DrhgqZZWuzu1U= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.2 h1:AhmO1fHINP9vFYUE0LHzCWg/LfUWUF+zFPEcY9QXb7o= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.2/go.mod h1:o8aQygT2+MVP0NaV6kbdE1YnnIM8RRVQzoeUH45GOdI= github.com/aws/aws-sdk-go-v2/service/sts v1.32.2 h1:CiS7i0+FUe+/YY1GvIBLLrR/XNGZ4CtM1Ll0XavNuVo= github.com/aws/aws-sdk-go-v2/service/sts v1.32.2/go.mod h1:HtaiBI8CjYoNVde8arShXb94UbQQi9L4EMr6D+xGBwo= github.com/aws/smithy-go v1.22.0 h1:uunKnWlcoL3zO7q+gG2Pk53joueEOsnNB28QdMsmiMM= github.com/aws/smithy-go v1.22.0/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0 h1:UGZ1QwZWY67Z6BmckTU+9Rxn04m2bD3gD6Mk0OIOCPk= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0/go.mod h1:fcwWuDuaObkkChiDlhEpSq9+X1C0omv+s5mBtToAQ64= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= main.go000066400000000000000000000053151470323427300377170ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-lambda-go/otellambda/example// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package main import ( "context" "encoding/json" "log" "net/http" "github.com/aws/aws-lambda-go/lambda" awsConfig "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/service/s3" lambdadetector "go.opentelemetry.io/contrib/detectors/aws/lambda" "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda" "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" sdktrace "go.opentelemetry.io/otel/sdk/trace" ) func lambdaHandler(ctx context.Context) error { // init aws config cfg, err := awsConfig.LoadDefaultConfig(ctx) if err != nil { return err } // instrument all aws clients otelaws.AppendMiddlewares(&cfg.APIOptions) // S3 s3Client := s3.NewFromConfig(cfg) input := &s3.ListBucketsInput{} result, err := s3Client.ListBuckets(ctx, input) if err != nil { return err } log.Println("Buckets:") for _, bucket := range result.Buckets { log.Println(*bucket.Name + ": " + bucket.CreationDate.Format("2006-01-02 15:04:05 Monday")) } // HTTP client := &http.Client{ Transport: otelhttp.NewTransport( http.DefaultTransport, otelhttp.WithTracerProvider(otel.GetTracerProvider()), ), } req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.github.com/repos/open-telemetry/opentelemetry-go/releases/latest", nil) if err != nil { log.Printf("failed to create http request, %v\n", err) return err } res, err := client.Do(req) if err != nil { log.Printf("failed to do http request, %v\n", err) return err } defer func() { err := res.Body.Close() if err != nil { log.Printf("failed to close http response body, %v\n", err) } }() var data map[string]interface{} err = json.NewDecoder(res.Body).Decode(&data) if err != nil { log.Printf("failed to read http response body, %v\n", err) } log.Printf("Latest OTel Go Release is '%s'\n", data["name"]) return nil } func main() { ctx := context.Background() exp, err := stdouttrace.New() if err != nil { log.Printf("failed to initialize stdout exporter %v\n", err) return } detector := lambdadetector.NewResourceDetector() res, err := detector.Detect(ctx) if err != nil { log.Fatalf("failed to detect lambda resources: %v\n", err) return } tp := sdktrace.NewTracerProvider( sdktrace.WithSyncer(exp), sdktrace.WithResource(res), ) // Downstream spans use global tracer provider otel.SetTracerProvider(tp) lambda.Start(otellambda.InstrumentHandler(lambdaHandler, otellambda.WithTracerProvider(tp))) } manualAWSCleanup.sh000077500000000000000000000044531470323427300421450ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-lambda-go/otellambda/example#!/bin/sh # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 # constants LAMBDA_FUNCTION_NAME=SampleLambdaGo ROLE_NAME="$LAMBDA_FUNCTION_NAME"Role POLICY_NAME="$LAMBDA_FUNCTION_NAME"Policy LOG_GROUP_NAME=/aws/lambda/"$LAMBDA_FUNCTION_NAME" AWS_ACCT_ID=$(aws sts get-caller-identity | jq '.Account | tonumber') ERROR_LOG_FILE=manualAWSCleanupErrors.log # Clear log rm $ERROR_LOG_FILE 2> /dev/null # clear log group of all streams if aws logs describe-log-streams --log-group-name "$LOG_GROUP_NAME" > /dev/null 2>> $ERROR_LOG_FILE ; then LOG_STREAM_NAME=$(aws logs describe-log-streams --log-group-name "$LOG_GROUP_NAME" --order-by LastEventTime --descending | jq --raw-output '.logStreams[0].logStreamName') while [ "$LOG_STREAM_NAME" != "null" ] ; do aws logs delete-log-stream --log-group-name "$LOG_GROUP_NAME" --log-stream-name "$LOG_STREAM_NAME" 2>> $ERROR_LOG_FILE && echo "Deleted log stream $LOG_STREAM_NAME" LOG_STREAM_NAME=$(aws logs describe-log-streams --log-group-name "$LOG_GROUP_NAME" --order-by LastEventTime --descending | jq --raw-output '.logStreams[0].logStreamName') done aws logs delete-log-group --log-group-name "$LOG_GROUP_NAME" && echo "Deleted log group $LOG_GROUP_NAME" else echo "Did not delete log group, likely already deleted" fi # destroy remaining lambda resources if they exist aws lambda delete-function --function-name "$LAMBDA_FUNCTION_NAME" 2>> $ERROR_LOG_FILE && echo "Deleted Lambda Function $LAMBDA_FUNCTION_NAME" || echo "Did not delete function, likely already deleted" aws iam detach-role-policy --role-name "$ROLE_NAME" --policy-arn arn:aws:iam::"$AWS_ACCT_ID":policy/"$POLICY_NAME" 2>> $ERROR_LOG_FILE && echo "Detached $POLICY_NAME from $ROLE_NAME" || echo "Did not detach policy from role, likely already detached" aws iam delete-policy --policy-arn arn:aws:iam::"$AWS_ACCT_ID":policy/"$POLICY_NAME" 2>> $ERROR_LOG_FILE && echo "Deleted IAM Policy POLICY_NAME" || echo "Did not delete IAM Policy, likely already deleted" aws iam delete-role --role-name "$ROLE_NAME" 2>> $ERROR_LOG_FILE && echo "Deleted IAM Role $ROLE_NAME" || echo "Did not delete IAM Role, likely already deleted" if [ -s $ERROR_LOG_FILE ] ; then echo 'Some resources failed to delete. Can ensure these errors were due to the resources existing by checking "'$ERROR_LOG_FILE'"' fipolicyForRoleDocument.json000066400000000000000000000005301470323427300436200ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-lambda-go/otellambda/example{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "s3:ListAllMyBuckets", "Resource": "*" }, { "Sid": "", "Effect": "Allow", "Action": [ "logs:PutLogEvents", "logs:CreateLogStream", "logs:CreateLogGroup" ], "Resource": "*" } ] }go.mod000066400000000000000000000010451470323427300361130ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-lambda-go/otellambdamodule go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda go 1.22 require ( github.com/aws/aws-lambda-go v1.47.0 github.com/stretchr/testify v1.9.0 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/trace v1.31.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) go.sum000066400000000000000000000041761470323427300361500ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-lambda-go/otellambdagithub.com/aws/aws-lambda-go v1.47.0 h1:0H8s0vumYx/YKs4sE7YM0ktwL2eWse+kfopsRI1sXVI= github.com/aws/aws-lambda-go v1.47.0/go.mod h1:dpMpZgvWx5vuQJfBt0zqBha60q7Dd7RfgJv23DymV8A= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= lambda.go000066400000000000000000000056561470323427300365700ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-lambda-go/otellambda// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otellambda // import "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda" import ( "context" "log" "os" "strings" "github.com/aws/aws-lambda-go/lambdacontext" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" semconv "go.opentelemetry.io/otel/semconv/v1.21.0" "go.opentelemetry.io/otel/trace" ) const ( // ScopeName is the instrumentation scope name. ScopeName = "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda" ) var errorLogger = log.New(log.Writer(), "OTel Lambda Error: ", 0) type instrumentor struct { configuration config resAttrs []attribute.KeyValue tracer trace.Tracer } func newInstrumentor(opts ...Option) instrumentor { cfg := config{ TracerProvider: otel.GetTracerProvider(), Flusher: &noopFlusher{}, EventToCarrier: emptyEventToCarrier, Propagator: otel.GetTextMapPropagator(), } for _, opt := range opts { opt.apply(&cfg) } return instrumentor{ configuration: cfg, tracer: cfg.TracerProvider.Tracer(ScopeName, trace.WithInstrumentationVersion(Version())), resAttrs: []attribute.KeyValue{}, } } // Logic to start OTel Tracing. func (i *instrumentor) tracingBegin(ctx context.Context, eventJSON []byte) (context.Context, trace.Span) { // Add trace id to context mc := i.configuration.EventToCarrier(eventJSON) ctx = i.configuration.Propagator.Extract(ctx, mc) var span trace.Span spanName := os.Getenv("AWS_LAMBDA_FUNCTION_NAME") var attributes []attribute.KeyValue lc, ok := lambdacontext.FromContext(ctx) if !ok { errorLogger.Println("failed to load lambda context from context, ensure tracing enabled in Lambda") } if lc != nil { ctxRequestID := lc.AwsRequestID attributes = append(attributes, semconv.FaaSInvocationID(ctxRequestID)) // Some resource attrs added as span attrs because lambda // resource detectors are created before a lambda // invocation and therefore lack lambdacontext. // Create these attrs upon first invocation if len(i.resAttrs) == 0 { ctxFunctionArn := lc.InvokedFunctionArn attributes = append(attributes, semconv.AWSLambdaInvokedARN(ctxFunctionArn)) arnParts := strings.Split(ctxFunctionArn, ":") if len(arnParts) >= 5 { attributes = append(attributes, semconv.CloudAccountID(arnParts[4])) } } attributes = append(attributes, i.resAttrs...) } ctx, span = i.tracer.Start(ctx, spanName, trace.WithSpanKind(trace.SpanKindServer), trace.WithAttributes(attributes...)) return ctx, span } // Logic to wrap up OTel Tracing. func (i *instrumentor) tracingEnd(ctx context.Context, span trace.Span) { span.End() // force flush any tracing data since lambda may freeze err := i.configuration.Flusher.ForceFlush(ctx) if err != nil { errorLogger.Println("failed to force a flush, lambda may freeze before instrumentation exported: ", err) } } lambda_test.go000066400000000000000000000207661470323427300376260ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-lambda-go/otellambda// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otellambda import ( "context" "encoding/json" "errors" "fmt" "os" "reflect" "testing" "github.com/aws/aws-lambda-go/lambda" "github.com/aws/aws-lambda-go/lambda/messages" "github.com/aws/aws-lambda-go/lambdacontext" "github.com/stretchr/testify/assert" ) var ( mockLambdaContext = lambdacontext.LambdaContext{ AwsRequestID: "123", InvokedFunctionArn: "arn:partition:service:region:account-id:resource-type:resource-id", Identity: lambdacontext.CognitoIdentity{ CognitoIdentityID: "someId", CognitoIdentityPoolID: "somePoolId", }, ClientContext: lambdacontext.ClientContext{}, } mockContext = lambdacontext.NewContext(context.TODO(), &mockLambdaContext) ) type emptyHandler struct{} func (h emptyHandler) Invoke(_ context.Context, _ []byte) ([]byte, error) { return nil, nil } var _ lambda.Handler = emptyHandler{} func setEnvVars() { _ = os.Setenv("AWS_LAMBDA_FUNCTION_NAME", "testFunction") _ = os.Setenv("AWS_REGION", "us-texas-1") _ = os.Setenv("AWS_LAMBDA_FUNCTION_VERSION", "$LATEST") _ = os.Setenv("AWS_LAMBDA_LOG_STREAM_NAME", "2023/01/01/[$LATEST]5d1edb9e525d486696cf01a3503487bc") _ = os.Setenv("AWS_LAMBDA_FUNCTION_MEMORY_SIZE", "128") _ = os.Setenv("_X_AMZN_TRACE_ID", "Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1") } func TestLambdaHandlerSignatures(t *testing.T) { setEnvVars() emptyPayload := "" testCases := []struct { name string handler interface{} expected error args []reflect.Value }{ { name: "nil handler", expected: errors.New("handler is nil"), handler: nil, args: []reflect.Value{reflect.ValueOf(mockContext), reflect.ValueOf(emptyPayload)}, }, { name: "handler is not a function", expected: errors.New("handler kind struct is not func"), handler: struct{}{}, args: []reflect.Value{reflect.ValueOf(mockContext), reflect.ValueOf(emptyPayload)}, }, { name: "handler declares too many arguments", expected: errors.New("handlers may not take more than two arguments, but handler takes 3"), handler: func(n context.Context, x string, y string) error { return nil }, args: []reflect.Value{reflect.ValueOf(mockContext), reflect.ValueOf(emptyPayload)}, }, { name: "two argument handler does not have context as first argument", expected: errors.New("handler takes two arguments, but the first is not Context. got string"), handler: func(a string, x context.Context) error { return nil }, args: []reflect.Value{reflect.ValueOf(mockContext), reflect.ValueOf(emptyPayload)}, }, { name: "handler returns too many values", expected: errors.New("handler may not return more than two values"), handler: func() (error, error, error) { return nil, nil, nil }, args: []reflect.Value{reflect.ValueOf(mockContext), reflect.ValueOf(emptyPayload)}, }, { name: "handler returning two values does not declare error as the second return value", expected: errors.New("handler returns two values, but the second does not implement error"), handler: func() (error, string) { return nil, "hello" }, args: []reflect.Value{reflect.ValueOf(mockContext), reflect.ValueOf(emptyPayload)}, }, { name: "handler returning a single value does not implement error", expected: errors.New("handler returns a single value, but it does not implement error"), handler: func() string { return "hello" }, args: []reflect.Value{reflect.ValueOf(mockContext), reflect.ValueOf(emptyPayload)}, }, { name: "no args or return value should not result in error", expected: nil, handler: func() { }, args: []reflect.Value{reflect.ValueOf(mockContext)}, // reminder - customer takes no args but wrapped handler always takes context from lambda }, } for i, testCase := range testCases { testCase := testCase t.Run(fmt.Sprintf("testCase[%d] %s", i, testCase.name), func(t *testing.T) { lambdaHandler := InstrumentHandler(testCase.handler) handler := reflect.ValueOf(lambdaHandler) resp := handler.Call(testCase.args) assert.Len(t, resp, 2) assert.Equal(t, testCase.expected, resp[1].Interface()) }) } } type expected struct { val interface{} err error } func TestHandlerInvokes(t *testing.T) { setEnvVars() hello := func(s string) string { return fmt.Sprintf("Hello %s!", s) } testCases := []struct { name string input interface{} expected expected handler interface{} }{ { name: "string input and return without context", input: "Lambda", expected: expected{`"Hello Lambda!"`, nil}, handler: func(name string) (string, error) { return hello(name), nil }, }, { name: "string input and return with context", input: "Lambda", expected: expected{`"Hello Lambda!"`, nil}, handler: func(ctx context.Context, name string) (string, error) { return hello(name), nil }, }, { name: "no input with response event and simple error", input: nil, expected: expected{"", errors.New("bad stuff")}, handler: func() (interface{}, error) { return nil, errors.New("bad stuff") }, }, { name: "input with response event and simple error", input: "Lambda", expected: expected{"", errors.New("bad stuff")}, handler: func(e interface{}) (interface{}, error) { return nil, errors.New("bad stuff") }, }, { name: "input and context with response event and simple error", input: "Lambda", expected: expected{"", errors.New("bad stuff")}, handler: func(ctx context.Context, e interface{}) (interface{}, error) { return nil, errors.New("bad stuff") }, }, { name: "input with response event and complex error", input: "Lambda", expected: expected{"", messages.InvokeResponse_Error{Message: "message", Type: "type"}}, handler: func(e interface{}) (interface{}, error) { return nil, messages.InvokeResponse_Error{Message: "message", Type: "type"} }, }, { name: "basic input struct serialization", input: struct{ Custom int }{9001}, expected: expected{`9001`, nil}, handler: func(event struct{ Custom int }) (int, error) { return event.Custom, nil }, }, { name: "basic output struct serialization", input: 9001, expected: expected{`{"Number":9001}`, nil}, handler: func(event int) (struct{ Number int }, error) { return struct{ Number int }{event}, nil }, }, } // test invocation via a lambda handler for i, testCase := range testCases { testCase := testCase t.Run(fmt.Sprintf("lambdaHandlerTestCase[%d] %s", i, testCase.name), func(t *testing.T) { lambdaHandler := InstrumentHandler(testCase.handler) handler := reflect.ValueOf(lambdaHandler) handlerType := handler.Type() var args []reflect.Value args = append(args, reflect.ValueOf(mockContext)) if handlerType.NumIn() > 1 { args = append(args, reflect.ValueOf(testCase.input)) } response := handler.Call(args) assert.Len(t, response, 2) if testCase.expected.err != nil { assert.Equal(t, testCase.expected.err, response[handlerType.NumOut()-1].Interface()) } else { assert.Nil(t, response[handlerType.NumOut()-1].Interface()) responseValMarshalled, _ := json.Marshal(response[0].Interface()) assert.Equal(t, testCase.expected.val, string(responseValMarshalled)) } }) } // test invocation via a Handler for i, testCase := range testCases { testCase := testCase t.Run(fmt.Sprintf("handlerTestCase[%d] %s", i, testCase.name), func(t *testing.T) { handler := WrapHandler(lambda.NewHandler(testCase.handler)) inputPayload, _ := json.Marshal(testCase.input) response, err := handler.Invoke(mockContext, inputPayload) if testCase.expected.err != nil { assert.Equal(t, testCase.expected.err, err) } else { assert.NoError(t, err) assert.Equal(t, testCase.expected.val, string(response)) } }) } } func BenchmarkInstrumentHandler(b *testing.B) { setEnvVars() customerHandler := func(ctx context.Context, payload int) error { return nil } wrapped := InstrumentHandler(customerHandler) wrappedCallable := reflect.ValueOf(wrapped) ctx := reflect.ValueOf(mockContext) payload := reflect.ValueOf(0) args := []reflect.Value{ctx, payload} b.ResetTimer() for i := 0; i < b.N; i++ { wrappedCallable.Call(args) } } func BenchmarkWrapHandler(b *testing.B) { setEnvVars() wrapped := WrapHandler(emptyHandler{}) b.ResetTimer() for i := 0; i < b.N; i++ { _, _ = wrapped.Invoke(mockContext, []byte{0}) } } test/000077500000000000000000000000001470323427300357645ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-lambda-go/otellambdadoc.go000066400000000000000000000006751470323427300370700ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-lambda-go/otellambda/test// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 /* Package test validates AWS Lambda instrumentation with the default SDK. This package is in a separate module from the instrumentation it tests to isolate the dependency of the default SDK and not impose this as a transitive dependency for users. */ package test // import "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda/test" go.mod000066400000000000000000000022041470323427300370700ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-lambda-go/otellambda/testmodule go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda/test go 1.22 replace ( go.opentelemetry.io/contrib/detectors/aws/lambda => ../../../../../../detectors/aws/lambda go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda => ../ go.opentelemetry.io/contrib/propagators/aws => ../../../../../../propagators/aws ) require ( github.com/aws/aws-lambda-go v1.47.0 github.com/stretchr/testify v1.9.0 go.opentelemetry.io/contrib/detectors/aws/lambda v0.56.0 go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda v0.56.0 go.opentelemetry.io/contrib/propagators/aws v1.31.0 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/sdk v1.31.0 go.opentelemetry.io/otel/trace v1.31.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect golang.org/x/sys v0.26.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) go.sum000066400000000000000000000051531470323427300371230ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-lambda-go/otellambda/testgithub.com/aws/aws-lambda-go v1.47.0 h1:0H8s0vumYx/YKs4sE7YM0ktwL2eWse+kfopsRI1sXVI= github.com/aws/aws-lambda-go v1.47.0/go.mod h1:dpMpZgvWx5vuQJfBt0zqBha60q7Dd7RfgJv23DymV8A= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= lambda_test.go000066400000000000000000000327241470323427300406020ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-lambda-go/otellambda/test// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package test import ( "context" "encoding/json" "fmt" "log" "reflect" "strconv" "strings" "sync" "testing" "time" "github.com/aws/aws-lambda-go/lambda" "github.com/aws/aws-lambda-go/lambdacontext" "github.com/stretchr/testify/assert" lambdadetector "go.opentelemetry.io/contrib/detectors/aws/lambda" "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda" "go.opentelemetry.io/contrib/propagators/aws/xray" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/sdk/instrumentation" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/sdk/trace/tracetest" semconv "go.opentelemetry.io/otel/semconv/v1.26.0" "go.opentelemetry.io/otel/trace" ) var errorLogger = log.New(log.Writer(), "OTel Lambda Test Error: ", 0) type mockIDGenerator struct { sync.Mutex traceCount int spanCount int } func (m *mockIDGenerator) NewIDs(_ context.Context) (trace.TraceID, trace.SpanID) { m.Lock() defer m.Unlock() m.traceCount++ m.spanCount++ return [16]byte{byte(m.traceCount)}, [8]byte{byte(m.spanCount)} } func (m *mockIDGenerator) NewSpanID(_ context.Context, _ trace.TraceID) trace.SpanID { m.Lock() defer m.Unlock() m.spanCount++ return [8]byte{byte(m.spanCount)} } var _ sdktrace.IDGenerator = &mockIDGenerator{} type emptyHandler struct{} func (h emptyHandler) Invoke(_ context.Context, _ []byte) ([]byte, error) { return nil, nil } var _ lambda.Handler = emptyHandler{} func initMockTracerProvider() (*sdktrace.TracerProvider, *tracetest.InMemoryExporter) { ctx := context.Background() exp := tracetest.NewInMemoryExporter() detector := lambdadetector.NewResourceDetector() res, err := detector.Detect(ctx) if err != nil { errorLogger.Printf("failed to detect lambda resources: %v\n", err) return nil, nil } tp := sdktrace.NewTracerProvider( sdktrace.WithSyncer(exp), sdktrace.WithIDGenerator(&mockIDGenerator{}), sdktrace.WithResource(res), ) return tp, exp } func setEnvVars(t *testing.T) { t.Setenv("AWS_LAMBDA_FUNCTION_NAME", "testFunction") t.Setenv("AWS_REGION", "us-texas-1") t.Setenv("AWS_LAMBDA_FUNCTION_VERSION", "$LATEST") t.Setenv("AWS_LAMBDA_LOG_STREAM_NAME", "2023/01/01/[$LATEST]5d1edb9e525d486696cf01a3503487bc") t.Setenv("AWS_LAMBDA_FUNCTION_MEMORY_SIZE", "128") t.Setenv("_X_AMZN_TRACE_ID", "Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1") } // Vars for Tracing and TracingWithFlusher Tests. var ( mockLambdaContext = lambdacontext.LambdaContext{ AwsRequestID: "123", InvokedFunctionArn: "arn:partition:service:region:account-id:resource-type:resource-id", Identity: lambdacontext.CognitoIdentity{ CognitoIdentityID: "someId", CognitoIdentityPoolID: "somePoolId", }, ClientContext: lambdacontext.ClientContext{}, } mockContext = xray.Propagator{}.Extract(lambdacontext.NewContext(context.TODO(), &mockLambdaContext), propagation.HeaderCarrier{ "X-Amzn-Trace-Id": []string{"Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1"}, }) expectedTraceID, _ = trace.TraceIDFromHex("5759e988bd862e3fe1be46a994272793") expectedSpanStub = tracetest.SpanStub{ Name: "testFunction", SpanContext: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: expectedTraceID, SpanID: trace.SpanID{1}, TraceFlags: 1, TraceState: trace.TraceState{}, Remote: false, }), Parent: trace.SpanContextFromContext(mockContext), SpanKind: trace.SpanKindServer, StartTime: time.Time{}, EndTime: time.Time{}, Attributes: []attribute.KeyValue{ attribute.String("faas.invocation_id", "123"), attribute.String("aws.lambda.invoked_arn", "arn:partition:service:region:account-id:resource-type:resource-id"), attribute.String("cloud.account.id", "account-id"), }, Events: nil, Links: nil, Status: sdktrace.Status{}, DroppedAttributes: 0, DroppedEvents: 0, DroppedLinks: 0, ChildSpanCount: 0, Resource: resource.NewWithAttributes(semconv.SchemaURL, attribute.String("cloud.provider", "aws"), attribute.String("cloud.region", "us-texas-1"), attribute.String("faas.name", "testFunction"), attribute.String("faas.version", "$LATEST"), attribute.String("faas.instance", "2023/01/01/[$LATEST]5d1edb9e525d486696cf01a3503487bc"), attribute.Int("faas.max_memory", 128)), InstrumentationScope: instrumentation.Scope{ Name: "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda", Version: otellambda.Version(), }, } ) func assertStubEqualsIgnoreTime(t *testing.T, expected tracetest.SpanStub, actual tracetest.SpanStub) { assert.Equal(t, expected.Name, actual.Name) assert.Equal(t, expected.SpanContext, actual.SpanContext) assert.Equal(t, expected.Parent, actual.Parent) assert.Equal(t, expected.SpanKind, actual.SpanKind) assert.Equal(t, expected.Attributes, actual.Attributes) assert.Equal(t, expected.Events, actual.Events) assert.Equal(t, expected.Links, actual.Links) assert.Equal(t, expected.Status, actual.Status) assert.Equal(t, expected.DroppedAttributes, actual.DroppedAttributes) assert.Equal(t, expected.DroppedEvents, actual.DroppedEvents) assert.Equal(t, expected.DroppedLinks, actual.DroppedLinks) assert.Equal(t, expected.ChildSpanCount, actual.ChildSpanCount) assert.Equal(t, expected.Resource, actual.Resource) assert.Equal(t, expected.InstrumentationScope, actual.InstrumentationScope) } func TestInstrumentHandlerTracing(t *testing.T) { setEnvVars(t) tp, memExporter := initMockTracerProvider() customerHandler := func() (string, error) { return "hello world", nil } // No flusher needed as SimpleSpanProcessor is synchronous wrapped := otellambda.InstrumentHandler(customerHandler, otellambda.WithTracerProvider(tp)) wrappedCallable := reflect.ValueOf(wrapped) resp := wrappedCallable.Call([]reflect.Value{reflect.ValueOf(mockContext)}) assert.Len(t, resp, 2) assert.Equal(t, "hello world", resp[0].Interface()) assert.Nil(t, resp[1].Interface()) assert.Len(t, memExporter.GetSpans(), 1) stub := memExporter.GetSpans()[0] assertStubEqualsIgnoreTime(t, expectedSpanStub, stub) } func TestWrapHandlerTracing(t *testing.T) { setEnvVars(t) tp, memExporter := initMockTracerProvider() // No flusher needed as SimpleSpanProcessor is synchronous wrapped := otellambda.WrapHandler(emptyHandler{}, otellambda.WithTracerProvider(tp)) _, err := wrapped.Invoke(mockContext, []byte{}) assert.NoError(t, err) assert.Len(t, memExporter.GetSpans(), 1) stub := memExporter.GetSpans()[0] assertStubEqualsIgnoreTime(t, expectedSpanStub, stub) } type mockFlusher struct { flushCount int } func (mf *mockFlusher) ForceFlush(context.Context) error { mf.flushCount++ return nil } var _ otellambda.Flusher = &mockFlusher{} func TestInstrumentHandlerTracingWithFlusher(t *testing.T) { setEnvVars(t) tp, memExporter := initMockTracerProvider() customerHandler := func() (string, error) { return "hello world", nil } flusher := mockFlusher{} wrapped := otellambda.InstrumentHandler(customerHandler, otellambda.WithTracerProvider(tp), otellambda.WithFlusher(&flusher)) wrappedCallable := reflect.ValueOf(wrapped) resp := wrappedCallable.Call([]reflect.Value{reflect.ValueOf(mockContext)}) assert.Len(t, resp, 2) assert.Equal(t, "hello world", resp[0].Interface()) assert.Nil(t, resp[1].Interface()) assert.Len(t, memExporter.GetSpans(), 1) stub := memExporter.GetSpans()[0] assertStubEqualsIgnoreTime(t, expectedSpanStub, stub) assert.Equal(t, 1, flusher.flushCount) } func TestWrapHandlerTracingWithFlusher(t *testing.T) { setEnvVars(t) tp, memExporter := initMockTracerProvider() flusher := mockFlusher{} wrapped := otellambda.WrapHandler(emptyHandler{}, otellambda.WithTracerProvider(tp), otellambda.WithFlusher(&flusher)) _, err := wrapped.Invoke(mockContext, []byte{}) assert.NoError(t, err) assert.Len(t, memExporter.GetSpans(), 1) stub := memExporter.GetSpans()[0] assertStubEqualsIgnoreTime(t, expectedSpanStub, stub) assert.Equal(t, 1, flusher.flushCount) } const mockPropagatorKey = "Mockkey" type mockPropagator struct{} func (prop mockPropagator) Extract(ctx context.Context, carrier propagation.TextMapCarrier) context.Context { // extract tracing information if header := carrier.Get(mockPropagatorKey); header != "" { scc := trace.SpanContextConfig{} splitHeaderVal := strings.Split(header, ":") var err error scc.TraceID, err = trace.TraceIDFromHex(splitHeaderVal[0]) if err != nil { errorLogger.Println("Failed to create trace id from hex: ", err) } scc.SpanID, err = trace.SpanIDFromHex(splitHeaderVal[1]) if err != nil { errorLogger.Println("Failed to create span id from hex: ", err) } isTraced, err := strconv.Atoi(splitHeaderVal[1]) if err != nil { errorLogger.Println("Failed to convert trace flag to int: ", err) } scc.TraceFlags = scc.TraceFlags.WithSampled(isTraced != 0) sc := trace.NewSpanContext(scc) return trace.ContextWithRemoteSpanContext(ctx, sc) } return ctx } func (prop mockPropagator) Inject(context.Context, propagation.TextMapCarrier) { // not needed other than to satisfy interface } func (prop mockPropagator) Fields() []string { // not needed other than to satisfy interface return []string{} } type mockRequest struct { Headers map[string]string } // Vars for mockPropagator Tests. var ( mockPropagatorTestsTraceIDHex = "12345678901234567890123456789012" mockPropagatorTestsSpanIDHex = "1234567890123456" mockPropagatorTestsSampled = "1" mockPropagatorTestsHeader = mockPropagatorTestsTraceIDHex + ":" + mockPropagatorTestsSpanIDHex + ":" + mockPropagatorTestsSampled mockPropagatorTestsEvent = mockRequest{Headers: map[string]string{mockPropagatorKey: mockPropagatorTestsHeader}} mockPropagatorTestsContext = mockPropagator{}.Extract(lambdacontext.NewContext(context.TODO(), &mockLambdaContext), propagation.HeaderCarrier{mockPropagatorKey: []string{mockPropagatorTestsHeader}}) mockPropagatorTestsExpectedTraceID, _ = trace.TraceIDFromHex(mockPropagatorTestsTraceIDHex) mockPropagatorTestsExpectedSpanStub = tracetest.SpanStub{ Name: "testFunction", SpanContext: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: mockPropagatorTestsExpectedTraceID, SpanID: trace.SpanID{1}, TraceFlags: 1, TraceState: trace.TraceState{}, Remote: false, }), Parent: trace.SpanContextFromContext(mockPropagatorTestsContext), SpanKind: trace.SpanKindServer, StartTime: time.Time{}, EndTime: time.Time{}, Attributes: []attribute.KeyValue{ attribute.String("faas.invocation_id", "123"), attribute.String("aws.lambda.invoked_arn", "arn:partition:service:region:account-id:resource-type:resource-id"), attribute.String("cloud.account.id", "account-id"), }, Events: nil, Links: nil, Status: sdktrace.Status{}, DroppedAttributes: 0, DroppedEvents: 0, DroppedLinks: 0, ChildSpanCount: 0, Resource: resource.NewWithAttributes(semconv.SchemaURL, attribute.String("cloud.provider", "aws"), attribute.String("cloud.region", "us-texas-1"), attribute.String("faas.name", "testFunction"), attribute.String("faas.version", "$LATEST"), attribute.String("faas.instance", "2023/01/01/[$LATEST]5d1edb9e525d486696cf01a3503487bc"), attribute.Int("faas.max_memory", 128)), InstrumentationScope: instrumentation.Scope{ Name: "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda", Version: otellambda.Version(), }, } ) func mockRequestCarrier(eventJSON []byte) propagation.TextMapCarrier { var event mockRequest err := json.Unmarshal(eventJSON, &event) if err != nil { fmt.Println("event type: ", reflect.TypeOf(event)) panic("mockRequestCarrier only supports events of type mockRequest") } return propagation.HeaderCarrier{mockPropagatorKey: []string{event.Headers[mockPropagatorKey]}} } func TestInstrumentHandlerTracingWithMockPropagator(t *testing.T) { setEnvVars(t) tp, memExporter := initMockTracerProvider() customerHandler := func(event mockRequest) (string, error) { return "hello world", nil } // No flusher needed as SimpleSpanProcessor is synchronous wrapped := otellambda.InstrumentHandler(customerHandler, otellambda.WithTracerProvider(tp), otellambda.WithPropagator(mockPropagator{}), otellambda.WithEventToCarrier(mockRequestCarrier)) wrappedCallable := reflect.ValueOf(wrapped) resp := wrappedCallable.Call([]reflect.Value{reflect.ValueOf(mockPropagatorTestsContext), reflect.ValueOf(mockPropagatorTestsEvent)}) assert.Len(t, resp, 2) assert.Equal(t, "hello world", resp[0].Interface()) assert.Nil(t, resp[1].Interface()) assert.Len(t, memExporter.GetSpans(), 1) stub := memExporter.GetSpans()[0] assertStubEqualsIgnoreTime(t, mockPropagatorTestsExpectedSpanStub, stub) } func TestWrapHandlerTracingWithMockPropagator(t *testing.T) { setEnvVars(t) tp, memExporter := initMockTracerProvider() // No flusher needed as SimpleSpanProcessor is synchronous wrapped := otellambda.WrapHandler(emptyHandler{}, otellambda.WithTracerProvider(tp), otellambda.WithPropagator(mockPropagator{}), otellambda.WithEventToCarrier(mockRequestCarrier)) payload, _ := json.Marshal(mockPropagatorTestsEvent) _, err := wrapped.Invoke(mockPropagatorTestsContext, payload) assert.NoError(t, err) assert.Len(t, memExporter.GetSpans(), 1) stub := memExporter.GetSpans()[0] assertStubEqualsIgnoreTime(t, mockPropagatorTestsExpectedSpanStub, stub) } version.go000066400000000000000000000010521470323427300370170ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-lambda-go/otellambda// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otellambda // import "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda" // Version is the current release version of the AWS Lambda instrumentation. func Version() string { return "0.56.0" // This string is updated by the pre_release.sh script during release } // SemVersion is the semantic version to be supplied to tracer/meter creation. // // Deprecated: Use [Version] instead. func SemVersion() string { return Version() } wrapHandler.go000066400000000000000000000023251470323427300376050ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-lambda-go/otellambda// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otellambda // import "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda" import ( "context" "github.com/aws/aws-lambda-go/lambda" ) // wrappedHandler is a struct which holds an instrumentor // as well as the user's original lambda.Handler and is // able to instrument invocations of the user's lambda.Handler. type wrappedHandler struct { instrumentor instrumentor handler lambda.Handler } // Compile time check our Handler implements lambda.Handler. var _ lambda.Handler = wrappedHandler{} // Invoke adds OTel span surrounding customer Handler invocation. func (h wrappedHandler) Invoke(ctx context.Context, payload []byte) ([]byte, error) { ctx, span := h.instrumentor.tracingBegin(ctx, payload) defer h.instrumentor.tracingEnd(ctx, span) response, err := h.handler.Invoke(ctx, payload) if err != nil { return nil, err } return response, nil } // WrapHandler Provides a Handler which wraps customer Handler with OTel Tracing. func WrapHandler(handler lambda.Handler, options ...Option) lambda.Handler { return wrappedHandler{instrumentor: newInstrumentor(options...), handler: handler} } wrapLambdaHandler.go000066400000000000000000000136731470323427300407160ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-lambda-go/otellambda// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otellambda // import "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda" import ( "context" "encoding/json" "fmt" "reflect" ) // wrappedHandlerFunction is a struct which only holds an instrumentor and is // able to instrument invocations of the user's lambda handler function. type wrappedHandlerFunction struct { instrumentor instrumentor } func errorHandler(e error) func(context.Context, interface{}) (interface{}, error) { return func(context.Context, interface{}) (interface{}, error) { return nil, e } } // Ensure handler takes 0-2 values, with context // as its first value if two arguments exist. func validateArguments(handler reflect.Type) (bool, error) { handlerTakesContext := false if handler.NumIn() > 2 { return false, fmt.Errorf("handlers may not take more than two arguments, but handler takes %d", handler.NumIn()) } else if handler.NumIn() > 0 { contextType := reflect.TypeOf((*context.Context)(nil)).Elem() argumentType := handler.In(0) handlerTakesContext = argumentType.Implements(contextType) if handler.NumIn() > 1 && !handlerTakesContext { return false, fmt.Errorf("handler takes two arguments, but the first is not Context. got %s", argumentType.Kind()) } } return handlerTakesContext, nil } // Ensure handler returns 0-2 values, with an error // as its first value if any exist. func validateReturns(handler reflect.Type) error { errorType := reflect.TypeOf((*error)(nil)).Elem() switch n := handler.NumOut(); { case n > 2: return fmt.Errorf("handler may not return more than two values") case n == 2: if !handler.Out(1).Implements(errorType) { return fmt.Errorf("handler returns two values, but the second does not implement error") } case n == 1: if !handler.Out(0).Implements(errorType) { return fmt.Errorf("handler returns a single value, but it does not implement error") } } return nil } // Wraps and calls customer lambda handler then unpacks response as necessary. func (whf *wrappedHandlerFunction) wrapperInternals(ctx context.Context, handlerFunc interface{}, eventJSON []byte, event reflect.Value, takesContext bool) (interface{}, error) { wrappedLambdaHandler := reflect.ValueOf(whf.wrapper(handlerFunc)) argsWrapped := []reflect.Value{reflect.ValueOf(ctx), reflect.ValueOf(eventJSON), event, reflect.ValueOf(takesContext)} response := wrappedLambdaHandler.Call(argsWrapped)[0].Interface().([]reflect.Value) // convert return values into (interface{}, error) var err error if len(response) > 0 { if errVal, ok := response[len(response)-1].Interface().(error); ok { err = errVal } } var val interface{} if len(response) > 1 { val = response[0].Interface() } return val, err } // InstrumentHandler Provides a lambda handler which wraps customer lambda handler with OTel Tracing. func InstrumentHandler(handlerFunc interface{}, options ...Option) interface{} { whf := wrappedHandlerFunction{instrumentor: newInstrumentor(options...)} if handlerFunc == nil { return errorHandler(fmt.Errorf("handler is nil")) } handlerType := reflect.TypeOf(handlerFunc) if handlerType.Kind() != reflect.Func { return errorHandler(fmt.Errorf("handler kind %s is not %s", handlerType.Kind(), reflect.Func)) } takesContext, err := validateArguments(handlerType) if err != nil { return errorHandler(err) } if err := validateReturns(handlerType); err != nil { return errorHandler(err) } // note we will always take context to capture lambda context, // regardless of whether customer takes context if handlerType.NumIn() == 0 || handlerType.NumIn() == 1 && takesContext { return func(ctx context.Context) (interface{}, error) { var temp *interface{} event := reflect.ValueOf(temp) return whf.wrapperInternals(ctx, handlerFunc, []byte{}, event, takesContext) } } // customer either takes both context and payload or just payload return func(ctx context.Context, payload interface{}) (interface{}, error) { event := reflect.New(handlerType.In(handlerType.NumIn() - 1)) // lambda SDK normally unmarshalls to customer event type, however // with the wrapper the SDK unmarshalls to map[string]interface{} // due to our use of reflection. Therefore we must convert this map // to customer's desired event, we do so by simply re-marshaling then // unmarshalling to the desired event type. The remarshalledPayload // will also be used by users using custom propagators remarshalledPayload, err := json.Marshal(payload) if err != nil { return nil, err } if err := json.Unmarshal(remarshalledPayload, event.Interface()); err != nil { return nil, err } return whf.wrapperInternals(ctx, handlerFunc, remarshalledPayload, event.Elem(), takesContext) } } // Adds OTel span surrounding customer handler call. func (whf *wrappedHandlerFunction) wrapper(handlerFunc interface{}) func(ctx context.Context, eventJSON []byte, event interface{}, takesContext bool) []reflect.Value { return func(ctx context.Context, eventJSON []byte, event interface{}, takesContext bool) []reflect.Value { ctx, span := whf.instrumentor.tracingBegin(ctx, eventJSON) defer whf.instrumentor.tracingEnd(ctx, span) handler := reflect.ValueOf(handlerFunc) var args []reflect.Value if takesContext { args = append(args, reflect.ValueOf(ctx)) } if eventExists(event) { args = append(args, reflect.ValueOf(event)) } response := handler.Call(args) return response } } // Determine if an interface{} is nil or the // if the reflect.Value of the event is nil. func eventExists(event interface{}) bool { if event == nil { return false } // reflect.Value.isNil() can only be called on // Values of certain Kinds. Unsupported Kinds // will panic rather than return false switch reflect.TypeOf(event).Kind() { case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.UnsafePointer, reflect.Interface, reflect.Slice: return !reflect.ValueOf(event).IsNil() } return true } xrayconfig/000077500000000000000000000000001470323427300371565ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-lambda-go/otellambdaREADME.md000066400000000000000000000075721470323427300404500ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-lambda-go/otellambda/xrayconfig# Recommended Configurations for OpenTelemetry AWS Lambda Instrumentation with AWS X-Ray [![Go Reference][goref-image]][goref-url] [![Apache License][license-image]][license-url] This module provides recommended configuration options for [`AWS Lambda Instrumentation`](https://github.com/open-telemetry/opentelemetry-go-contrib/tree/main/instrumentation/github.com/aws/aws-lambda-go/otellambda) when using [AWS X-Ray](https://aws.amazon.com/xray/). By using this configuration, trace context will automatically be extracted from incoming requests with the `X-Amzn-Trace-Id` header if present. Trace context will also always be injected using the `X-Amzn-Trace-Id` format into downstream requests from the Lambda function. ## Installation ```bash go get -u go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda/xrayconfig ``` ## Usage Create a sample Lambda Go application instrumented by the `otellambda` package such as below. ```go package main import ( "context" "fmt" "github.com/aws/aws-lambda-go/lambda" "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda" ) type MyEvent struct { Name string `json:"name"` } func HandleRequest(ctx context.Context, name MyEvent) (string, error) { return fmt.Sprintf("Hello %s!", name.Name ), nil } func main() { lambda.Start(otellambda.InstrumentHandler(HandleRequest)) } ``` Now configure the instrumentation with the provided options to export traces to AWS X-Ray via [the OpenTelemetry Collector](https://github.com/open-telemetry/opentelemetry-collector) running as a Lambda Extension. Instructions for running the OTel Collector as a Lambda Extension can be found in the [AWS OpenTelemetry Documentation](https://aws-otel.github.io/docs/getting-started/lambda). ```go // Add import import "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda/xrayconfig" // add options to InstrumentHandler call func main() { lambda.Start(otellambda.InstrumentHandler(HandleRequest, xrayconfig.WithRecommendedOptions()...)) } ``` ## Recommended AWS Lambda Instrumentation Options | Instrumentation Option | Recommended Value | Exported As | | --- | --- | --- | | `WithTracerProvider` | An `sdktrace.TracerProvider` configured to export in batches to an OTel Collector running locally in Lambda | Not individually exported. Can only be used via `WithRecommendedOptions()` | `WithFlusher` | An `otellambda.Flusher` which yields before calling ForceFlush on the configured `sdktrace.TracerProvider`. Yielding mitigates data delays caused by asynchronous nature of batching TracerProvider when in Lambda | Not individually exported. Can only be used via `WithRecommendedOptions()` | `WithEventToCarrier` | Function which reads X-Ray TraceID from Lambda environment and inserts it into a `propagtation.TextMapCarrier` | Individually exported as `WithEventToCarrier()`, also included in `WithRecommendedOptions()` | `WithPropagator` | An `xray.propagator` | Individually exported as `WithPropagator()`, also included in `WithRecommendedOptions()` ## Useful links - For more information on OpenTelemetry, visit: - For more about OpenTelemetry Go: - For help or feedback on this project, join us in [GitHub Discussions][discussions-url] ## License Apache 2.0 - See [LICENSE][license-url] for more information. [license-url]: https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/LICENSE [license-image]: https://img.shields.io/badge/license-Apache_2.0-green.svg?style=flat [goref-image]: https://pkg.go.dev/badge/go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda/xrayconfig.svg [goref-url]: https://pkg.go.dev/go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda/xrayconfig [discussions-url]: https://github.com/open-telemetry/opentelemetry-go/discussions collector_test.go000066400000000000000000000045761470323427300425460ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-lambda-go/otellambda/xrayconfig// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package xrayconfig // Pared down version of go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal/otlptracetest/collector.go // for end to end testing import ( "sort" collectortracepb "go.opentelemetry.io/proto/otlp/collector/trace/v1" commonpb "go.opentelemetry.io/proto/otlp/common/v1" resourcepb "go.opentelemetry.io/proto/otlp/resource/v1" tracepb "go.opentelemetry.io/proto/otlp/trace/v1" ) // SpansStorage stores the spans. Mock collectors can use it to // store spans they have received. type SpansStorage struct { rsm map[string]*tracepb.ResourceSpans spanCount int } // NewSpansStorage creates a new spans storage. func NewSpansStorage() SpansStorage { return SpansStorage{ rsm: make(map[string]*tracepb.ResourceSpans), } } // AddSpans adds spans to the spans storage. func (s *SpansStorage) AddSpans(request *collectortracepb.ExportTraceServiceRequest) { for _, rs := range request.GetResourceSpans() { rstr := resourceString(rs.Resource) if existingRs, ok := s.rsm[rstr]; !ok { s.rsm[rstr] = rs // TODO (rghetia): Add support for library Info. if len(rs.ScopeSpans) == 0 { rs.ScopeSpans = []*tracepb.ScopeSpans{ { Spans: []*tracepb.Span{}, }, } } s.spanCount += len(rs.ScopeSpans[0].Spans) } else { if len(rs.ScopeSpans) > 0 { newSpans := rs.ScopeSpans[0].GetSpans() existingRs.ScopeSpans[0].Spans = append(existingRs.ScopeSpans[0].Spans, newSpans...) s.spanCount += len(newSpans) } } } } // GetSpans returns the stored spans. func (s *SpansStorage) GetSpans() []*tracepb.Span { spans := make([]*tracepb.Span, 0, s.spanCount) for _, rs := range s.rsm { spans = append(spans, rs.ScopeSpans[0].Spans...) } return spans } // GetResourceSpans returns the stored resource spans. func (s *SpansStorage) GetResourceSpans() []*tracepb.ResourceSpans { rss := make([]*tracepb.ResourceSpans, 0, len(s.rsm)) for _, rs := range s.rsm { rss = append(rss, rs) } return rss } func resourceString(res *resourcepb.Resource) string { sAttrs := sortedAttributes(res.GetAttributes()) rstr := "" for _, attr := range sAttrs { rstr = rstr + attr.String() } return rstr } func sortedAttributes(attrs []*commonpb.KeyValue) []*commonpb.KeyValue { sort.Slice(attrs[:], func(i, j int) bool { return attrs[i].Key < attrs[j].Key }) return attrs } go.mod000066400000000000000000000034041470323427300402650ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-lambda-go/otellambda/xrayconfigmodule go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda/xrayconfig go 1.22 replace ( go.opentelemetry.io/contrib/detectors/aws/lambda => ../../../../../../detectors/aws/lambda go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda => ../ go.opentelemetry.io/contrib/propagators/aws => ../../../../../../propagators/aws ) require ( github.com/aws/aws-lambda-go v1.47.0 github.com/stretchr/testify v1.9.0 go.opentelemetry.io/contrib/detectors/aws/lambda v0.56.0 go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda v0.56.0 go.opentelemetry.io/contrib/propagators/aws v1.31.0 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0 go.opentelemetry.io/otel/sdk v1.31.0 go.opentelemetry.io/otel/trace v1.31.0 go.opentelemetry.io/proto/otlp v1.3.1 google.golang.org/grpc v1.67.1 ) require ( github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect golang.org/x/net v0.30.0 // indirect golang.org/x/sys v0.26.0 // indirect golang.org/x/text v0.19.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 // indirect google.golang.org/protobuf v1.35.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) go.sum000066400000000000000000000127561470323427300403240ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-lambda-go/otellambda/xrayconfiggithub.com/aws/aws-lambda-go v1.47.0 h1:0H8s0vumYx/YKs4sE7YM0ktwL2eWse+kfopsRI1sXVI= github.com/aws/aws-lambda-go v1.47.0/go.mod h1:dpMpZgvWx5vuQJfBt0zqBha60q7Dd7RfgJv23DymV8A= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 h1:K0XaT3DwHAcV4nKLzcQvwAgSyisUghWoY20I7huthMk= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0/go.mod h1:B5Ki776z/MBnVha1Nzwp5arlzBbE3+1jk+pGmaP5HME= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0 h1:FFeLy03iVTXP6ffeN2iXrxfGsZGCjVx0/4KlizjyBwU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0/go.mod h1:TMu73/k1CP8nBUpDLc71Wj/Kf7ZS9FK5b53VapRsP9o= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 h1:T6rh4haD3GVYsgEfWExoCZA2o2FmbNyKpTuAxbEFPTg= google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:wp2WsuBYj6j8wUdo3ToZsdxxixbvQNAHqVJrTgi5E5M= google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 h1:QCqS/PdaHTSWGvupk2F/ehwHtGc0/GYkT+3GAcR1CCc= google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= mock_collector_test.go000066400000000000000000000105631470323427300435500ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-lambda-go/otellambda/xrayconfig// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package xrayconfig // Pared down version of go.opentelemtry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/mock_collector_test.go // for end to end testing import ( "context" "errors" "fmt" "net" "runtime" "sync" "testing" "time" "google.golang.org/grpc" "google.golang.org/grpc/metadata" collectortracepb "go.opentelemetry.io/proto/otlp/collector/trace/v1" tracepb "go.opentelemetry.io/proto/otlp/trace/v1" ) func makeMockCollector(t *testing.T, mockConfig *mockConfig) *mockCollector { return &mockCollector{ t: t, traceSvc: &mockTraceService{ storage: NewSpansStorage(), errors: mockConfig.errors, }, } } type mockTraceService struct { collectortracepb.UnimplementedTraceServiceServer errors []error requests int mu sync.RWMutex storage SpansStorage headers metadata.MD delay time.Duration } func (mts *mockTraceService) getResourceSpans() []*tracepb.ResourceSpans { mts.mu.RLock() defer mts.mu.RUnlock() return mts.storage.GetResourceSpans() } func (mts *mockTraceService) Export(ctx context.Context, exp *collectortracepb.ExportTraceServiceRequest) (*collectortracepb.ExportTraceServiceResponse, error) { if mts.delay > 0 { time.Sleep(mts.delay) } mts.mu.Lock() defer func() { mts.requests++ mts.mu.Unlock() }() reply := &collectortracepb.ExportTraceServiceResponse{} if mts.requests < len(mts.errors) { idx := mts.requests return reply, mts.errors[idx] } mts.headers, _ = metadata.FromIncomingContext(ctx) mts.storage.AddSpans(exp) return reply, nil } type mockCollector struct { t *testing.T traceSvc *mockTraceService endpoint string ln *listener stopFunc func() stopOnce sync.Once } type mockConfig struct { errors []error endpoint string } var _ collectortracepb.TraceServiceServer = (*mockTraceService)(nil) var errAlreadyStopped = fmt.Errorf("already stopped") func (mc *mockCollector) stop() error { err := errAlreadyStopped mc.stopOnce.Do(func() { err = nil if mc.stopFunc != nil { mc.stopFunc() } }) // Give it sometime to shutdown. <-time.After(160 * time.Millisecond) // Getting the lock ensures the traceSvc is done flushing. mc.traceSvc.mu.Lock() defer mc.traceSvc.mu.Unlock() return err } func (mc *mockCollector) Stop() error { return mc.stop() } func (mc *mockCollector) getResourceSpans() []*tracepb.ResourceSpans { return mc.traceSvc.getResourceSpans() } func (mc *mockCollector) GetResourceSpans() []*tracepb.ResourceSpans { return mc.getResourceSpans() } func runMockCollectorAtEndpoint(t *testing.T, endpoint string) *mockCollector { return runMockCollectorWithConfig(t, &mockConfig{endpoint: endpoint}) } func runMockCollectorWithConfig(t *testing.T, mockConfig *mockConfig) *mockCollector { ln, err := net.Listen("tcp", mockConfig.endpoint) if err != nil { t.Fatalf("Failed to get an endpoint: %v", err) } srv := grpc.NewServer() mc := makeMockCollector(t, mockConfig) collectortracepb.RegisterTraceServiceServer(srv, mc.traceSvc) mc.ln = newListener(ln) go func() { _ = srv.Serve((net.Listener)(mc.ln)) }() mc.endpoint = ln.Addr().String() // srv.Stop calls Close on mc.ln. mc.stopFunc = srv.Stop return mc } type listener struct { closeOnce sync.Once wrapped net.Listener C chan struct{} } func newListener(wrapped net.Listener) *listener { return &listener{ wrapped: wrapped, C: make(chan struct{}, 1), } } func (l *listener) Close() error { return l.wrapped.Close() } func (l *listener) Addr() net.Addr { return l.wrapped.Addr() } // Accept waits for and returns the next connection to the listener. It will // send a signal on l.C that a connection has been made before returning. func (l *listener) Accept() (net.Conn, error) { conn, err := l.wrapped.Accept() if err != nil { if errors.Is(err, net.ErrClosed) { // If the listener has been closed, do not allow callers of // WaitForConn to wait for a connection that will never come. l.closeOnce.Do(func() { close(l.C) }) } return conn, err } select { case l.C <- struct{}{}: default: // If C is full, assume nobody is listening and move on. } return conn, nil } // WaitForConn will wait indefintely for a connection to be established with // the listener before returning. func (l *listener) WaitForConn() { for { select { case <-l.C: return default: runtime.Gosched() } } } xrayconfig.go000066400000000000000000000045061470323427300416630ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-lambda-go/otellambda/xrayconfig// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package xrayconfig // import "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda/xrayconfig" import ( "context" "os" lambdadetector "go.opentelemetry.io/contrib/detectors/aws/lambda" "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda" "go.opentelemetry.io/contrib/propagators/aws/xray" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "go.opentelemetry.io/otel/propagation" sdktrace "go.opentelemetry.io/otel/sdk/trace" //nolint:depguard // NewTracerProvider requires the SDK ) func xrayEventToCarrier([]byte) propagation.TextMapCarrier { xrayTraceID := os.Getenv("_X_AMZN_TRACE_ID") return propagation.HeaderCarrier{"X-Amzn-Trace-Id": []string{xrayTraceID}} } // NewTracerProvider returns a TracerProvider configured with an exporter, // ID generator, and lambda resource detector to send trace data to AWS X-Ray // via a Collector instance listening on localhost. func NewTracerProvider(ctx context.Context) (*sdktrace.TracerProvider, error) { exp, err := otlptracegrpc.New(ctx, otlptracegrpc.WithInsecure()) if err != nil { return nil, err } detector := lambdadetector.NewResourceDetector() resource, err := detector.Detect(ctx) if err != nil { return nil, err } return sdktrace.NewTracerProvider( sdktrace.WithBatcher(exp), sdktrace.WithIDGenerator(xray.NewIDGenerator()), sdktrace.WithResource(resource), ), nil } // WithEventToCarrier returns an otellambda.Option to enable // an otellambda.EventToCarrier function which reads the XRay trace // information from the environment and returns this information in // a propagation.HeaderCarrier. func WithEventToCarrier() otellambda.Option { return otellambda.WithEventToCarrier(xrayEventToCarrier) } // WithPropagator returns an otellambda.Option to enable the xray.Propagator. func WithPropagator() otellambda.Option { return otellambda.WithPropagator(xray.Propagator{}) } // WithRecommendedOptions returns a list of all otellambda.Option(s) // recommended for the otellambda package when using AWS XRay. func WithRecommendedOptions(tp *sdktrace.TracerProvider) []otellambda.Option { return []otellambda.Option{WithEventToCarrier(), WithPropagator(), otellambda.WithTracerProvider(tp), otellambda.WithFlusher(tp)} } xrayconfig_test.go000066400000000000000000000173021470323427300427200ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-lambda-go/otellambda/xrayconfig// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package xrayconfig import ( "context" "os" "reflect" "runtime" "testing" "time" "github.com/aws/aws-lambda-go/lambdacontext" "github.com/stretchr/testify/assert" "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda" "go.opentelemetry.io/contrib/propagators/aws/xray" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" v1common "go.opentelemetry.io/proto/otlp/common/v1" v1resource "go.opentelemetry.io/proto/otlp/resource/v1" v1trace "go.opentelemetry.io/proto/otlp/trace/v1" ) func TestEventToCarrier(t *testing.T) { t.Setenv("_X_AMZN_TRACE_ID", "traceID") carrier := xrayEventToCarrier([]byte{}) assert.Equal(t, "traceID", carrier.Get("X-Amzn-Trace-Id")) } func TestEventToCarrierWithPropagator(t *testing.T) { t.Setenv("_X_AMZN_TRACE_ID", "Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1") carrier := xrayEventToCarrier([]byte{}) ctx := xray.Propagator{}.Extract(context.Background(), carrier) expectedTraceID, _ := trace.TraceIDFromHex("5759e988bd862e3fe1be46a994272793") expectedSpanID, _ := trace.SpanIDFromHex("53995c3f42cd8ad8") expectedCtx := trace.ContextWithRemoteSpanContext(context.Background(), trace.NewSpanContext(trace.SpanContextConfig{ TraceID: expectedTraceID, SpanID: expectedSpanID, TraceFlags: trace.FlagsSampled, TraceState: trace.TraceState{}, Remote: true, })) assert.Equal(t, expectedCtx, ctx) } func setEnvVars(t *testing.T) { t.Setenv("AWS_LAMBDA_FUNCTION_NAME", "testFunction") t.Setenv("AWS_REGION", "us-texas-1") t.Setenv("AWS_LAMBDA_FUNCTION_VERSION", "$LATEST") t.Setenv("AWS_LAMBDA_LOG_STREAM_NAME", "2023/01/01/[$LATEST]5d1edb9e525d486696cf01a3503487bc") t.Setenv("AWS_LAMBDA_FUNCTION_MEMORY_SIZE", "128") t.Setenv("_X_AMZN_TRACE_ID", "Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1") // fix issue: "The requested service provider could not be loaded or initialized." // Guess: The env for Windows in GitHub action is incomplete if runtime.GOOS == "windows" && os.Getenv("SYSTEMROOT") == "" { t.Setenv("SYSTEMROOT", `C:\Windows`) } } // Vars for end to end testing. var ( mockLambdaContext = lambdacontext.LambdaContext{ AwsRequestID: "123", InvokedFunctionArn: "arn:partition:service:region:account-id:resource-type:resource-id", Identity: lambdacontext.CognitoIdentity{}, ClientContext: lambdacontext.ClientContext{}, } mockContext = xray.Propagator{}.Extract(lambdacontext.NewContext(context.Background(), &mockLambdaContext), propagation.HeaderCarrier{ "X-Amzn-Trace-Id": []string{"Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1"}, }) expectedSpans = v1trace.ScopeSpans{ Scope: &v1common.InstrumentationScope{Name: "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda", Version: otellambda.Version()}, Spans: []*v1trace.Span{{ TraceId: []byte{0x57, 0x59, 0xe9, 0x88, 0xbd, 0x86, 0x2e, 0x3f, 0xe1, 0xbe, 0x46, 0xa9, 0x94, 0x27, 0x27, 0x93}, SpanId: nil, TraceState: "", ParentSpanId: []byte{0x53, 0x99, 0x5c, 0x3f, 0x42, 0xcd, 0x8a, 0xd8}, Name: "testFunction", Kind: v1trace.Span_SPAN_KIND_SERVER, StartTimeUnixNano: 0, EndTimeUnixNano: 0, Attributes: []*v1common.KeyValue{ {Key: "faas.invocation_id", Value: &v1common.AnyValue{Value: &v1common.AnyValue_StringValue{StringValue: "123"}}}, {Key: "aws.lambda.invoked_arn", Value: &v1common.AnyValue{Value: &v1common.AnyValue_StringValue{StringValue: "arn:partition:service:region:account-id:resource-type:resource-id"}}}, {Key: "cloud.account.id", Value: &v1common.AnyValue{Value: &v1common.AnyValue_StringValue{StringValue: "account-id"}}}, }, DroppedAttributesCount: 0, Events: nil, DroppedEventsCount: 0, Links: nil, DroppedLinksCount: 0, Status: &v1trace.Status{Code: v1trace.Status_STATUS_CODE_UNSET}, }}, SchemaUrl: "", } expectedSpanResource = v1resource.Resource{ Attributes: []*v1common.KeyValue{ {Key: "cloud.provider", Value: &v1common.AnyValue{Value: &v1common.AnyValue_StringValue{StringValue: "aws"}}}, {Key: "cloud.region", Value: &v1common.AnyValue{Value: &v1common.AnyValue_StringValue{StringValue: "us-texas-1"}}}, {Key: "faas.instance", Value: &v1common.AnyValue{Value: &v1common.AnyValue_StringValue{StringValue: "2023/01/01/[$LATEST]5d1edb9e525d486696cf01a3503487bc"}}}, {Key: "faas.max_memory", Value: &v1common.AnyValue{Value: &v1common.AnyValue_IntValue{IntValue: 128}}}, {Key: "faas.name", Value: &v1common.AnyValue{Value: &v1common.AnyValue_StringValue{StringValue: "testFunction"}}}, {Key: "faas.version", Value: &v1common.AnyValue{Value: &v1common.AnyValue_StringValue{StringValue: "$LATEST"}}}, }, DroppedAttributesCount: 0, } expectedResourceSpans = v1trace.ResourceSpans{ Resource: &expectedSpanResource, ScopeSpans: []*v1trace.ScopeSpans{&expectedSpans}, SchemaUrl: "", } ) func assertResourceEquals(t *testing.T, expected *v1resource.Resource, actual *v1resource.Resource) { assert.Len(t, actual.Attributes, 6) assert.Equal(t, expected.Attributes[0].String(), actual.Attributes[0].String()) assert.Equal(t, expected.Attributes[1].String(), actual.Attributes[1].String()) assert.Equal(t, expected.Attributes[2].String(), actual.Attributes[2].String()) assert.Equal(t, expected.Attributes[3].String(), actual.Attributes[3].String()) assert.Equal(t, expected.Attributes[4].String(), actual.Attributes[4].String()) assert.Equal(t, expected.Attributes[5].String(), actual.Attributes[5].String()) assert.Equal(t, expected.DroppedAttributesCount, actual.DroppedAttributesCount) } // ignore timestamps and SpanID since time is obviously variable, // and SpanID is randomized when using xray IDGenerator. func assertSpanEqualsIgnoreTimeAndSpanID(t *testing.T, expected *v1trace.ResourceSpans, actual *v1trace.ResourceSpans) { assert.Equal(t, expected.ScopeSpans[0].Scope, actual.ScopeSpans[0].Scope) actualSpan := actual.ScopeSpans[0].Spans[0] expectedSpan := expected.ScopeSpans[0].Spans[0] assert.Equal(t, expectedSpan.Name, actualSpan.Name) assert.Equal(t, expectedSpan.ParentSpanId, actualSpan.ParentSpanId) assert.Equal(t, expectedSpan.Kind, actualSpan.Kind) assert.Equal(t, expectedSpan.Attributes, actualSpan.Attributes) assert.Equal(t, expectedSpan.Events, actualSpan.Events) assert.Equal(t, expectedSpan.Links, actualSpan.Links) assert.Equal(t, expectedSpan.Status, actualSpan.Status) assert.Equal(t, expectedSpan.DroppedAttributesCount, actualSpan.DroppedAttributesCount) assert.Equal(t, expectedSpan.DroppedEventsCount, actualSpan.DroppedEventsCount) assert.Equal(t, expectedSpan.DroppedLinksCount, actualSpan.DroppedLinksCount) assertResourceEquals(t, expected.Resource, actual.Resource) } func TestWrapEndToEnd(t *testing.T) { setEnvVars(t) ctx := context.Background() tp, err := NewTracerProvider(ctx) assert.NoError(t, err) customerHandler := func() (string, error) { return "hello world", nil } mockCollector := runMockCollectorAtEndpoint(t, "localhost:4317") defer func() { _ = mockCollector.Stop() }() <-time.After(5 * time.Millisecond) wrapped := otellambda.InstrumentHandler(customerHandler, WithRecommendedOptions(tp)...) wrappedCallable := reflect.ValueOf(wrapped) resp := wrappedCallable.Call([]reflect.Value{reflect.ValueOf(mockContext)}) assert.Len(t, resp, 2) assert.Equal(t, "hello world", resp[0].Interface()) assert.Nil(t, resp[1].Interface()) resSpans := mockCollector.getResourceSpans() assert.Len(t, resSpans, 1) assertSpanEqualsIgnoreTimeAndSpanID(t, &expectedResourceSpans, resSpans[0]) } open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-sdk-go-v2/000077500000000000000000000000001470323427300326465ustar00rootroot00000000000000otelaws/000077500000000000000000000000001470323427300342455ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-sdk-go-v2attributes.go000066400000000000000000000036621470323427300367710ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelaws // import "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws" import ( "context" v2Middleware "github.com/aws/aws-sdk-go-v2/aws/middleware" "github.com/aws/aws-sdk-go-v2/service/dynamodb" "github.com/aws/aws-sdk-go-v2/service/sqs" "github.com/aws/smithy-go/middleware" "go.opentelemetry.io/otel/attribute" semconv "go.opentelemetry.io/otel/semconv/v1.21.0" ) // AWS attributes. const ( RegionKey attribute.Key = "aws.region" RequestIDKey attribute.Key = "aws.request_id" AWSSystemVal string = "aws-api" ) var servicemap = map[string]AttributeSetter{ dynamodb.ServiceID: DynamoDBAttributeSetter, sqs.ServiceID: SQSAttributeSetter, } // SystemAttr return the AWS RPC system attribute. func SystemAttr() attribute.KeyValue { return semconv.RPCSystemKey.String(AWSSystemVal) } // OperationAttr returns the AWS operation attribute. func OperationAttr(operation string) attribute.KeyValue { return semconv.RPCMethod(operation) } // RegionAttr returns the AWS region attribute. func RegionAttr(region string) attribute.KeyValue { return RegionKey.String(region) } // ServiceAttr returns the AWS service attribute. func ServiceAttr(service string) attribute.KeyValue { return semconv.RPCService(service) } // RequestIDAttr returns the AWS request ID attribute. func RequestIDAttr(requestID string) attribute.KeyValue { return RequestIDKey.String(requestID) } // DefaultAttributeSetter checks to see if there are service specific attributes available to set for the AWS service. // If there are service specific attributes available then they will be included. func DefaultAttributeSetter(ctx context.Context, in middleware.InitializeInput) []attribute.KeyValue { serviceID := v2Middleware.GetServiceID(ctx) if fn, ok := servicemap[serviceID]; ok { return fn(ctx, in) } return []attribute.KeyValue{} } attributes_test.go000066400000000000000000000020101470323427300400120ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelaws import ( "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/attribute" semconv "go.opentelemetry.io/otel/semconv/v1.21.0" ) func TestOperationAttr(t *testing.T) { operation := "test-operation" attr := OperationAttr(operation) assert.Equal(t, attribute.String("rpc.method", operation), attr) } func TestRegionAttr(t *testing.T) { region := "test-region" attr := RegionAttr(region) assert.Equal(t, attribute.String("aws.region", region), attr) } func TestServiceAttr(t *testing.T) { service := "test-service" attr := ServiceAttr(service) assert.Equal(t, semconv.RPCService(service), attr) } func TestRequestIDAttr(t *testing.T) { requestID := "test-request-id" attr := RequestIDAttr(requestID) assert.Equal(t, attribute.String("aws.request_id", requestID), attr) } func TestSystemAttribute(t *testing.T) { attr := SystemAttr() assert.Equal(t, semconv.RPCSystemKey.String("aws-api"), attr) } aws.go000066400000000000000000000123701470323427300353710ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelaws // import "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws" import ( "context" "time" v2Middleware "github.com/aws/aws-sdk-go-v2/aws/middleware" "github.com/aws/smithy-go/middleware" smithyhttp "github.com/aws/smithy-go/transport/http" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/propagation" semconv "go.opentelemetry.io/otel/semconv/v1.21.0" "go.opentelemetry.io/otel/trace" ) const ( // ScopeName is the instrumentation scope name. ScopeName = "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws" ) type spanTimestampKey struct{} // AttributeSetter returns an array of KeyValue pairs, it can be used to set custom attributes. type AttributeSetter func(context.Context, middleware.InitializeInput) []attribute.KeyValue type otelMiddlewares struct { tracer trace.Tracer propagator propagation.TextMapPropagator attributeSetter []AttributeSetter } func (m otelMiddlewares) initializeMiddlewareBefore(stack *middleware.Stack) error { return stack.Initialize.Add(middleware.InitializeMiddlewareFunc("OTelInitializeMiddlewareBefore", func( ctx context.Context, in middleware.InitializeInput, next middleware.InitializeHandler) ( out middleware.InitializeOutput, metadata middleware.Metadata, err error, ) { ctx = context.WithValue(ctx, spanTimestampKey{}, time.Now()) return next.HandleInitialize(ctx, in) }), middleware.Before) } func (m otelMiddlewares) initializeMiddlewareAfter(stack *middleware.Stack) error { return stack.Initialize.Add(middleware.InitializeMiddlewareFunc("OTelInitializeMiddlewareAfter", func( ctx context.Context, in middleware.InitializeInput, next middleware.InitializeHandler) ( out middleware.InitializeOutput, metadata middleware.Metadata, err error, ) { serviceID := v2Middleware.GetServiceID(ctx) operation := v2Middleware.GetOperationName(ctx) region := v2Middleware.GetRegion(ctx) attributes := []attribute.KeyValue{ SystemAttr(), ServiceAttr(serviceID), RegionAttr(region), OperationAttr(operation), } for _, setter := range m.attributeSetter { attributes = append(attributes, setter(ctx, in)...) } ctx, span := m.tracer.Start(ctx, spanName(serviceID, operation), trace.WithTimestamp(ctx.Value(spanTimestampKey{}).(time.Time)), trace.WithSpanKind(trace.SpanKindClient), trace.WithAttributes(attributes...), ) defer span.End() out, metadata, err = next.HandleInitialize(ctx, in) if err != nil { span.RecordError(err) span.SetStatus(codes.Error, err.Error()) } return out, metadata, err }), middleware.After) } func (m otelMiddlewares) deserializeMiddleware(stack *middleware.Stack) error { return stack.Deserialize.Add(middleware.DeserializeMiddlewareFunc("OTelDeserializeMiddleware", func( ctx context.Context, in middleware.DeserializeInput, next middleware.DeserializeHandler) ( out middleware.DeserializeOutput, metadata middleware.Metadata, err error, ) { out, metadata, err = next.HandleDeserialize(ctx, in) resp, ok := out.RawResponse.(*smithyhttp.Response) if !ok { // No raw response to wrap with. return out, metadata, err } span := trace.SpanFromContext(ctx) span.SetAttributes(semconv.HTTPStatusCode(resp.StatusCode)) requestID, ok := v2Middleware.GetRequestIDMetadata(metadata) if ok { span.SetAttributes(RequestIDAttr(requestID)) } return out, metadata, err }), middleware.Before) } func (m otelMiddlewares) finalizeMiddleware(stack *middleware.Stack) error { return stack.Finalize.Add(middleware.FinalizeMiddlewareFunc("OTelFinalizeMiddleware", func( ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) ( out middleware.FinalizeOutput, metadata middleware.Metadata, err error, ) { // Propagate the Trace information by injecting it into the HTTP request. switch req := in.Request.(type) { case *smithyhttp.Request: m.propagator.Inject(ctx, propagation.HeaderCarrier(req.Header)) default: } return next.HandleFinalize(ctx, in) }), middleware.Before) } func spanName(serviceID, operation string) string { spanName := serviceID if operation != "" { spanName += "." + operation } return spanName } // AppendMiddlewares attaches OTel middlewares to the AWS Go SDK V2 for instrumentation. // OTel middlewares can be appended to either all aws clients or a specific operation. // Please see more details in https://aws.github.io/aws-sdk-go-v2/docs/middleware/ func AppendMiddlewares(apiOptions *[]func(*middleware.Stack) error, opts ...Option) { cfg := config{ TracerProvider: otel.GetTracerProvider(), TextMapPropagator: otel.GetTextMapPropagator(), } for _, opt := range opts { opt.apply(&cfg) } if cfg.AttributeSetter == nil { cfg.AttributeSetter = []AttributeSetter{DefaultAttributeSetter} } m := otelMiddlewares{ tracer: cfg.TracerProvider.Tracer(ScopeName, trace.WithInstrumentationVersion(Version())), propagator: cfg.TextMapPropagator, attributeSetter: cfg.AttributeSetter, } *apiOptions = append(*apiOptions, m.initializeMiddlewareBefore, m.initializeMiddlewareAfter, m.finalizeMiddleware, m.deserializeMiddleware) } aws_test.go000066400000000000000000000040431470323427300364260ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelaws import ( "context" "net/http" "testing" "github.com/aws/smithy-go/middleware" smithyhttp "github.com/aws/smithy-go/transport/http" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/propagation" ) type mockPropagator struct { injectKey string injectValue string } func (p mockPropagator) Inject(ctx context.Context, carrier propagation.TextMapCarrier) { carrier.Set(p.injectKey, p.injectValue) } func (p mockPropagator) Extract(ctx context.Context, carrier propagation.TextMapCarrier) context.Context { return context.TODO() } func (p mockPropagator) Fields() []string { return []string{} } func Test_otelMiddlewares_finalizeMiddleware(t *testing.T) { stack := middleware.Stack{ Finalize: middleware.NewFinalizeStep(), } propagator := mockPropagator{ injectKey: "mock-key", injectValue: "mock-value", } m := otelMiddlewares{ propagator: propagator, } err := m.finalizeMiddleware(&stack) require.NoError(t, err) input := &smithyhttp.Request{ Request: &http.Request{ Header: http.Header{}, }, } next := middleware.HandlerFunc(func(ctx context.Context, input interface{}) (output interface{}, metadata middleware.Metadata, err error) { return nil, middleware.Metadata{}, nil }) _, _, _ = stack.Finalize.HandleMiddleware(context.Background(), input, next) // Assert header has been updated with injected values key := http.CanonicalHeaderKey(propagator.injectKey) value := propagator.injectValue assert.Contains(t, input.Header, key) assert.Contains(t, input.Header[key], value) } func Test_Span_name(t *testing.T) { serviceID1 := "" serviceID2 := "ServiceID" operation1 := "" operation2 := "Operation" assert.Equal(t, "", spanName(serviceID1, operation1)) assert.Equal(t, spanName(serviceID1, operation2), "."+operation2) assert.Equal(t, spanName(serviceID2, operation1), serviceID2) assert.Equal(t, spanName(serviceID2, operation2), serviceID2+"."+operation2) } config.go000077500000000000000000000033631470323427300360510ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelaws // import "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws" import ( "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" ) type config struct { TracerProvider trace.TracerProvider TextMapPropagator propagation.TextMapPropagator AttributeSetter []AttributeSetter } // Option applies an option value. type Option interface { apply(*config) } // optionFunc provides a convenience wrapper for simple Options // that can be represented as functions. type optionFunc func(*config) func (o optionFunc) apply(c *config) { o(c) } // WithTracerProvider specifies a tracer provider to use for creating a tracer. // If none is specified, the global TracerProvider is used. func WithTracerProvider(provider trace.TracerProvider) Option { return optionFunc(func(cfg *config) { if provider != nil { cfg.TracerProvider = provider } }) } // WithTextMapPropagator specifies a Text Map Propagator to use when propagating context. // If none is specified, the global TextMapPropagator is used. func WithTextMapPropagator(propagator propagation.TextMapPropagator) Option { return optionFunc(func(cfg *config) { if propagator != nil { cfg.TextMapPropagator = propagator } }) } // WithAttributeSetter specifies an attribute setter function for setting service specific attributes. // If none is specified, the service will be determined by the DefaultAttributeSetter function and the corresponding attributes will be included. func WithAttributeSetter(attributesetters ...AttributeSetter) Option { return optionFunc(func(cfg *config) { cfg.AttributeSetter = append(cfg.AttributeSetter, attributesetters...) }) } config_test.go000066400000000000000000000006371470323427300371060ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelaws import ( "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel" ) func TestWithTextMapPropagator(t *testing.T) { cfg := config{} propagator := otel.GetTextMapPropagator() option := WithTextMapPropagator(propagator) option.apply(&cfg) assert.Equal(t, cfg.TextMapPropagator, propagator) } dynamodbattributes.go000066400000000000000000000150351470323427300405040ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelaws // import "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws" import ( "context" "encoding/json" "github.com/aws/aws-sdk-go-v2/service/dynamodb" "github.com/aws/smithy-go/middleware" "go.opentelemetry.io/otel/attribute" semconv "go.opentelemetry.io/otel/semconv/v1.21.0" ) // DynamoDBAttributeSetter sets DynamoDB specific attributes depending on the DynamoDB operation being performed. func DynamoDBAttributeSetter(ctx context.Context, in middleware.InitializeInput) []attribute.KeyValue { dynamodbAttributes := []attribute.KeyValue{semconv.DBSystemDynamoDB} switch v := in.Parameters.(type) { case *dynamodb.GetItemInput: dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBTableNames(*v.TableName)) if v.ConsistentRead != nil { dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBConsistentRead(*v.ConsistentRead)) } if v.ProjectionExpression != nil { dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBProjection(*v.ProjectionExpression)) } case *dynamodb.BatchGetItemInput: var tableNames []string for k := range v.RequestItems { tableNames = append(tableNames, k) } dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBTableNames(tableNames...)) case *dynamodb.BatchWriteItemInput: var tableNames []string for k := range v.RequestItems { tableNames = append(tableNames, k) } dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBTableNames(tableNames...)) case *dynamodb.CreateTableInput: dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBTableNames(*v.TableName)) if v.GlobalSecondaryIndexes != nil { var idx []string for _, gsi := range v.GlobalSecondaryIndexes { i, _ := json.Marshal(gsi) idx = append(idx, string(i)) } dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBGlobalSecondaryIndexes(idx...)) } if v.LocalSecondaryIndexes != nil { var idx []string for _, lsi := range v.LocalSecondaryIndexes { i, _ := json.Marshal(lsi) idx = append(idx, string(i)) } dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBLocalSecondaryIndexes(idx...)) } if v.ProvisionedThroughput != nil { read := float64(*v.ProvisionedThroughput.ReadCapacityUnits) dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBProvisionedReadCapacity(read)) write := float64(*v.ProvisionedThroughput.WriteCapacityUnits) dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBProvisionedWriteCapacity(write)) } case *dynamodb.DeleteItemInput: dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBTableNames(*v.TableName)) case *dynamodb.DeleteTableInput: dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBTableNames(*v.TableName)) case *dynamodb.DescribeTableInput: dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBTableNames(*v.TableName)) case *dynamodb.ListTablesInput: if v.ExclusiveStartTableName != nil { dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBExclusiveStartTable(*v.ExclusiveStartTableName)) } if v.Limit != nil { dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBLimit(int(*v.Limit))) } case *dynamodb.PutItemInput: dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBTableNames(*v.TableName)) case *dynamodb.QueryInput: dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBTableNames(*v.TableName)) if v.ConsistentRead != nil { dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBConsistentRead(*v.ConsistentRead)) } if v.IndexName != nil { dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBIndexName(*v.IndexName)) } if v.Limit != nil { dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBLimit(int(*v.Limit))) } if v.ScanIndexForward != nil { dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBScanForward(*v.ScanIndexForward)) } if v.ProjectionExpression != nil { dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBProjection(*v.ProjectionExpression)) } dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBSelect(string(v.Select))) case *dynamodb.ScanInput: dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBTableNames(*v.TableName)) if v.ConsistentRead != nil { dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBConsistentRead(*v.ConsistentRead)) } if v.IndexName != nil { dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBIndexName(*v.IndexName)) } if v.Limit != nil { dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBLimit(int(*v.Limit))) } if v.ProjectionExpression != nil { dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBProjection(*v.ProjectionExpression)) } dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBSelect(string(v.Select))) if v.Segment != nil { dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBSegment(int(*v.Segment))) } if v.TotalSegments != nil { dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBTotalSegments(int(*v.TotalSegments))) } case *dynamodb.UpdateItemInput: dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBTableNames(*v.TableName)) case *dynamodb.UpdateTableInput: dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBTableNames(*v.TableName)) if v.AttributeDefinitions != nil { var def []string for _, ad := range v.AttributeDefinitions { d, _ := json.Marshal(ad) def = append(def, string(d)) } dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBAttributeDefinitions(def...)) } if v.GlobalSecondaryIndexUpdates != nil { var idx []string for _, gsiu := range v.GlobalSecondaryIndexUpdates { i, _ := json.Marshal(gsiu) idx = append(idx, string(i)) } dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBGlobalSecondaryIndexUpdates(idx...)) } if v.ProvisionedThroughput != nil { read := float64(*v.ProvisionedThroughput.ReadCapacityUnits) dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBProvisionedReadCapacity(read)) write := float64(*v.ProvisionedThroughput.WriteCapacityUnits) dynamodbAttributes = append(dynamodbAttributes, semconv.AWSDynamoDBProvisionedWriteCapacity(write)) } } return dynamodbAttributes } dynamodbattributes_test.go000066400000000000000000000264241470323427300415470ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelaws import ( "context" "testing" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/dynamodb" dtypes "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" "github.com/aws/smithy-go/middleware" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/attribute" ) func TestDynamodbTagsBatchGetItemInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &dynamodb.BatchGetItemInput{ RequestItems: map[string]dtypes.KeysAndAttributes{ "table1": { Keys: []map[string]dtypes.AttributeValue{ { "id": &dtypes.AttributeValueMemberS{Value: "123"}, }, }, }, }, }, } attributes := DynamoDBAttributeSetter(context.TODO(), input) assert.Contains(t, attributes, attribute.StringSlice("aws.dynamodb.table_names", []string{"table1"})) } func TestDynamodbTagsBatchWriteItemInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &dynamodb.BatchWriteItemInput{ RequestItems: map[string][]dtypes.WriteRequest{ "table1": { { DeleteRequest: &dtypes.DeleteRequest{ Key: map[string]dtypes.AttributeValue{ "id": &dtypes.AttributeValueMemberS{Value: "123"}, }, }, }, { PutRequest: &dtypes.PutRequest{ Item: map[string]dtypes.AttributeValue{ "id": &dtypes.AttributeValueMemberS{Value: "234"}, }, }, }, }, }, }, } attributes := DynamoDBAttributeSetter(context.TODO(), input) assert.Contains(t, attributes, attribute.StringSlice("aws.dynamodb.table_names", []string{"table1"})) } func TestDynamodbTagsCreateTableInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &dynamodb.CreateTableInput{ AttributeDefinitions: []dtypes.AttributeDefinition{ { AttributeName: aws.String("id"), AttributeType: dtypes.ScalarAttributeTypeS, }, }, KeySchema: []dtypes.KeySchemaElement{ { AttributeName: aws.String("id"), KeyType: dtypes.KeyTypeHash, }, }, TableName: aws.String("table1"), BillingMode: dtypes.BillingModePayPerRequest, ProvisionedThroughput: &dtypes.ProvisionedThroughput{ ReadCapacityUnits: aws.Int64(123), WriteCapacityUnits: aws.Int64(456), }, GlobalSecondaryIndexes: []dtypes.GlobalSecondaryIndex{ { IndexName: aws.String("index1"), KeySchema: []dtypes.KeySchemaElement{ { AttributeName: aws.String("attributename"), KeyType: dtypes.KeyTypeHash, }, }, Projection: &dtypes.Projection{ NonKeyAttributes: []string{"non-key-attributes"}, }, }, }, LocalSecondaryIndexes: []dtypes.LocalSecondaryIndex{ { IndexName: aws.String("index2"), KeySchema: []dtypes.KeySchemaElement{ { AttributeName: aws.String("attributename"), KeyType: dtypes.KeyTypeHash, }, }, }, }, }, } attributes := DynamoDBAttributeSetter(context.TODO(), input) assert.Contains(t, attributes, attribute.StringSlice( "aws.dynamodb.table_names", []string{"table1"}, )) assert.Contains(t, attributes, attribute.StringSlice( "aws.dynamodb.global_secondary_indexes", []string{ `{"IndexName":"index1","KeySchema":[{"AttributeName":"attributename","KeyType":"HASH"}],"Projection":{"NonKeyAttributes":["non-key-attributes"],"ProjectionType":""},"OnDemandThroughput":null,"ProvisionedThroughput":null}`, }, )) assert.Contains(t, attributes, attribute.StringSlice( "aws.dynamodb.local_secondary_indexes", []string{ `{"IndexName":"index2","KeySchema":[{"AttributeName":"attributename","KeyType":"HASH"}],"Projection":null}`, }, )) assert.Contains(t, attributes, attribute.Float64("aws.dynamodb.provisioned_read_capacity", 123)) assert.Contains(t, attributes, attribute.Float64("aws.dynamodb.provisioned_write_capacity", 456)) } func TestDynamodbTagsDeleteItemInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &dynamodb.DeleteItemInput{ Key: map[string]dtypes.AttributeValue{ "id": &dtypes.AttributeValueMemberS{Value: "123"}, }, TableName: aws.String("table1"), }, } attributes := DynamoDBAttributeSetter(context.TODO(), input) assert.Contains(t, attributes, attribute.StringSlice( "aws.dynamodb.table_names", []string{"table1"}, )) } func TestDynamodbTagsDeleteTableInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &dynamodb.DeleteTableInput{ TableName: aws.String("table1"), }, } attributes := DynamoDBAttributeSetter(context.TODO(), input) assert.Contains(t, attributes, attribute.StringSlice( "aws.dynamodb.table_names", []string{"table1"}, )) } func TestDynamodbTagsDescribeTableInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &dynamodb.DescribeTableInput{ TableName: aws.String("table1"), }, } attributes := DynamoDBAttributeSetter(context.TODO(), input) assert.Contains(t, attributes, attribute.StringSlice( "aws.dynamodb.table_names", []string{"table1"}, )) } func TestDynamodbTagsListTablesInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &dynamodb.ListTablesInput{ ExclusiveStartTableName: aws.String("table1"), Limit: aws.Int32(10), }, } attributes := DynamoDBAttributeSetter(context.TODO(), input) assert.Contains(t, attributes, attribute.String("aws.dynamodb.exclusive_start_table", "table1")) assert.Contains(t, attributes, attribute.Int("aws.dynamodb.limit", 10)) } func TestDynamodbTagsPutItemInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &dynamodb.PutItemInput{ TableName: aws.String("table1"), Item: map[string]dtypes.AttributeValue{ "id": &dtypes.AttributeValueMemberS{Value: "12346"}, "name": &dtypes.AttributeValueMemberS{Value: "John Doe"}, "email": &dtypes.AttributeValueMemberS{Value: "john@doe.io"}, }, }, } attributes := DynamoDBAttributeSetter(context.TODO(), input) assert.Contains(t, attributes, attribute.StringSlice( "aws.dynamodb.table_names", []string{"table1"}, )) } func TestDynamodbTagsQueryInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &dynamodb.QueryInput{ TableName: aws.String("table1"), IndexName: aws.String("index1"), ConsistentRead: aws.Bool(true), Limit: aws.Int32(10), ScanIndexForward: aws.Bool(true), ProjectionExpression: aws.String("projectionexpression"), Select: dtypes.SelectAllAttributes, KeyConditionExpression: aws.String("id = :hashKey and #date > :rangeKey"), ExpressionAttributeNames: map[string]string{ "#date": "date", }, ExpressionAttributeValues: map[string]dtypes.AttributeValue{ ":hashKey": &dtypes.AttributeValueMemberS{Value: "123"}, ":rangeKey": &dtypes.AttributeValueMemberN{Value: "20150101"}, }, }, } attributes := DynamoDBAttributeSetter(context.TODO(), input) assert.Contains(t, attributes, attribute.StringSlice( "aws.dynamodb.table_names", []string{"table1"}, )) assert.Contains(t, attributes, attribute.Bool("aws.dynamodb.consistent_read", true)) assert.Contains(t, attributes, attribute.String("aws.dynamodb.index_name", "index1")) assert.Contains(t, attributes, attribute.Int("aws.dynamodb.limit", 10)) assert.Contains(t, attributes, attribute.Bool("aws.dynamodb.scan_forward", true)) assert.Contains(t, attributes, attribute.String("aws.dynamodb.projection", "projectionexpression")) assert.Contains(t, attributes, attribute.String("aws.dynamodb.select", "ALL_ATTRIBUTES")) } func TestDynamodbTagsScanInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &dynamodb.ScanInput{ TableName: aws.String("my-table"), ConsistentRead: aws.Bool(true), IndexName: aws.String("index1"), Limit: aws.Int32(10), ProjectionExpression: aws.String("Artist, Genre"), Segment: aws.Int32(10), TotalSegments: aws.Int32(100), Select: dtypes.SelectAllAttributes, }, } attributes := DynamoDBAttributeSetter(context.TODO(), input) assert.Contains(t, attributes, attribute.StringSlice( "aws.dynamodb.table_names", []string{"my-table"}, )) assert.Contains(t, attributes, attribute.Bool("aws.dynamodb.consistent_read", true)) assert.Contains(t, attributes, attribute.String("aws.dynamodb.index_name", "index1")) assert.Contains(t, attributes, attribute.Int("aws.dynamodb.limit", 10)) assert.Contains(t, attributes, attribute.String("aws.dynamodb.select", "ALL_ATTRIBUTES")) assert.Contains(t, attributes, attribute.Int("aws.dynamodb.total_segments", 100)) assert.Contains(t, attributes, attribute.Int("aws.dynamodb.segment", 10)) assert.Contains(t, attributes, attribute.String("aws.dynamodb.projection", "Artist, Genre")) } func TestDynamodbTagsUpdateItemInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &dynamodb.UpdateItemInput{ TableName: aws.String("my-table"), Key: map[string]dtypes.AttributeValue{ "id": &dtypes.AttributeValueMemberS{Value: "123"}, }, UpdateExpression: aws.String("set firstName = :firstName"), ExpressionAttributeValues: map[string]dtypes.AttributeValue{ ":firstName": &dtypes.AttributeValueMemberS{Value: "John McNewname"}, }, }, } attributes := DynamoDBAttributeSetter(context.TODO(), input) assert.Contains(t, attributes, attribute.StringSlice( "aws.dynamodb.table_names", []string{"my-table"}, )) } func TestDynamodbTagsUpdateTableInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &dynamodb.UpdateTableInput{ TableName: aws.String("my-table"), AttributeDefinitions: []dtypes.AttributeDefinition{ { AttributeName: aws.String("id"), AttributeType: dtypes.ScalarAttributeTypeS, }, }, GlobalSecondaryIndexUpdates: []dtypes.GlobalSecondaryIndexUpdate{ { Create: &dtypes.CreateGlobalSecondaryIndexAction{ IndexName: aws.String("index1"), KeySchema: []dtypes.KeySchemaElement{ { AttributeName: aws.String("attribute"), KeyType: dtypes.KeyTypeHash, }, }, Projection: &dtypes.Projection{ NonKeyAttributes: []string{"attribute1", "attribute2"}, ProjectionType: dtypes.ProjectionTypeAll, }, }, }, }, ProvisionedThroughput: &dtypes.ProvisionedThroughput{ ReadCapacityUnits: aws.Int64(123), WriteCapacityUnits: aws.Int64(456), }, }, } attributes := DynamoDBAttributeSetter(context.TODO(), input) assert.Contains(t, attributes, attribute.StringSlice( "aws.dynamodb.table_names", []string{"my-table"}, )) assert.Contains(t, attributes, attribute.StringSlice( "aws.dynamodb.attribute_definitions", []string{`{"AttributeName":"id","AttributeType":"S"}`}, )) assert.Contains(t, attributes, attribute.StringSlice( "aws.dynamodb.global_secondary_index_updates", []string{ `{"Create":{"IndexName":"index1","KeySchema":[{"AttributeName":"attribute","KeyType":"HASH"}],"Projection":{"NonKeyAttributes":["attribute1","attribute2"],"ProjectionType":"ALL"},"OnDemandThroughput":null,"ProvisionedThroughput":null},"Delete":null,"Update":null}`, }, )) assert.Contains(t, attributes, attribute.Float64("aws.dynamodb.provisioned_read_capacity", 123)) assert.Contains(t, attributes, attribute.Float64("aws.dynamodb.provisioned_write_capacity", 456)) } example/000077500000000000000000000000001470323427300357005ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-sdk-go-v2/otelawsDockerfile000066400000000000000000000004121470323427300376670ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/example# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 FROM golang:1.23-alpine AS base COPY . /src/ WORKDIR /src/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/example FROM base AS aws-client RUN go install ./main.go CMD ["/go/bin/main"] README.md000066400000000000000000000015531470323427300371630ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/example# aws/aws-sdk-go-v2 instrumentation example A simple example to demonstrate the AWS SDK V2 for Go instrumentation. In this example, container `aws-sdk-client` initializes a S3 client and a DynamoDB client and runs 2 basic operations: `listS3Buckets` and `listDynamodbTables`. These instructions assume you have [docker-compose](https://docs.docker.com/compose/) installed and setup, and [AWS credential](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html) configured. 1. From within the `example` directory, bring up the project by running: ```sh docker-compose up --detach ``` 2. The instrumentation works with a `stdout` exporter. To inspect the output, you can run: ```sh docker-compose logs ``` 3. After inspecting the client logs, the example can be cleaned up by running: ```sh docker-compose down ``` docker-compose.yml000066400000000000000000000005771470323427300413460ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/example# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 version: "3.7" services: aws-sdk-client: build: dockerfile: $PWD/Dockerfile context: ../../../../../.. ports: - "8080:80" command: - "/bin/sh" - "-c" - "/go/bin/main" volumes: - ~/.aws:/root/.aws networks: - example networks: example: go.mod000066400000000000000000000041211470323427300370040ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/examplemodule go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/example go 1.22 replace go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws => ../ require ( github.com/aws/aws-sdk-go-v2 v1.32.2 github.com/aws/aws-sdk-go-v2/config v1.27.43 github.com/aws/aws-sdk-go-v2/service/dynamodb v1.36.2 github.com/aws/aws-sdk-go-v2/service/s3 v1.65.3 go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws v0.56.0 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0 go.opentelemetry.io/otel/sdk v1.31.0 go.opentelemetry.io/otel/trace v1.31.0 ) require ( github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.6 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.17.41 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.17 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.21 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.21 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.21 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0 // indirect github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.2 // indirect github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.2 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.2 // indirect github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.2 // indirect github.com/aws/aws-sdk-go-v2/service/sqs v1.36.2 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.24.2 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.2 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.32.2 // indirect github.com/aws/smithy-go v1.22.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect golang.org/x/sys v0.26.0 // indirect ) go.sum000066400000000000000000000171401470323427300370360ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/examplegithub.com/aws/aws-sdk-go-v2 v1.32.2 h1:AkNLZEyYMLnx/Q/mSKkcMqwNFXMAvFto9bNsHqcTduI= github.com/aws/aws-sdk-go-v2 v1.32.2/go.mod h1:2SK5n0a2karNTv5tbP1SjsX0uhttou00v/HpXKM1ZUo= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.6 h1:pT3hpW0cOHRJx8Y0DfJUEQuqPild8jRGmSFmBgvydr0= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.6/go.mod h1:j/I2++U0xX+cr44QjHay4Cvxj6FUbnxrgmqN3H1jTZA= github.com/aws/aws-sdk-go-v2/config v1.27.43 h1:p33fDDihFC390dhhuv8nOmX419wjOSDQRb+USt20RrU= github.com/aws/aws-sdk-go-v2/config v1.27.43/go.mod h1:pYhbtvg1siOOg8h5an77rXle9tVG8T+BWLWAo7cOukc= github.com/aws/aws-sdk-go-v2/credentials v1.17.41 h1:7gXo+Axmp+R4Z+AK8YFQO0ZV3L0gizGINCOWxSLY9W8= github.com/aws/aws-sdk-go-v2/credentials v1.17.41/go.mod h1:u4Eb8d3394YLubphT4jLEwN1rLNq2wFOlT6OuxFwPzU= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.17 h1:TMH3f/SCAWdNtXXVPPu5D6wrr4G5hI1rAxbcocKfC7Q= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.17/go.mod h1:1ZRXLdTpzdJb9fwTMXiLipENRxkGMTn1sfKexGllQCw= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.21 h1:UAsR3xA31QGf79WzpG/ixT9FZvQlh5HY1NRqSHBNOCk= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.21/go.mod h1:JNr43NFf5L9YaG3eKTm7HQzls9J+A9YYcGI5Quh1r2Y= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.21 h1:6jZVETqmYCadGFvrYEQfC5fAQmlo80CeL5psbno6r0s= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.21/go.mod h1:1SR0GbLlnN3QUmYaflZNiH1ql+1qrSiB2vwcJ+4UM60= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.21 h1:7edmS3VOBDhK00b/MwGtGglCm7hhwNYnjJs/PgFdMQE= github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.21/go.mod h1:Q9o5h4HoIWG8XfzxqiuK/CGUbepCJ8uTlaE3bAbxytQ= github.com/aws/aws-sdk-go-v2/service/dynamodb v1.36.2 h1:kJqyYcGqhWFmXqjRrtFFD4Oc9FXiskhsll2xnlpe8Do= github.com/aws/aws-sdk-go-v2/service/dynamodb v1.36.2/go.mod h1:+t2Zc5VNOzhaWzpGE+cEYZADsgAAQT5v55AO+fhU+2s= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0 h1:TToQNkvGguu209puTojY/ozlqy2d/SFNcoLIqTFi42g= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0/go.mod h1:0jp+ltwkf+SwG2fm/PKo8t4y8pJSgOCO4D8Lz3k0aHQ= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.2 h1:4FMHqLfk0efmTqhXVRL5xYRqlEBNBiRI7N6w4jsEdd4= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.2/go.mod h1:LWoqeWlK9OZeJxsROW2RqrSPvQHKTpp69r/iDjwsSaw= github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.2 h1:1G7TTQNPNv5fhCyIQGYk8FOggLgkzKq6c4Y1nOGzAOE= github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.2/go.mod h1:+ybYGLXoF7bcD7wIcMcklxyABZQmuBf1cHUhvY6FGIo= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.2 h1:s7NA1SOw8q/5c0wr8477yOPp0z+uBaXBnLE0XYb0POA= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.2/go.mod h1:fnjjWyAW/Pj5HYOxl9LJqWtEwS7W2qgcRLWP+uWbss0= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.2 h1:t7iUP9+4wdc5lt3E41huP+GvQZJD38WLsgVp4iOtAjg= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.2/go.mod h1:/niFCtmuQNxqx9v8WAPq5qh7EH25U4BF6tjoyq9bObM= github.com/aws/aws-sdk-go-v2/service/s3 v1.65.3 h1:xxHGZ+wUgZNACQmxtdvP5tgzfsxGS3vPpTP5Hy3iToE= github.com/aws/aws-sdk-go-v2/service/s3 v1.65.3/go.mod h1:cB6oAuus7YXRZhWCc1wIwPywwZ1XwweNp2TVAEGYeB8= github.com/aws/aws-sdk-go-v2/service/sqs v1.36.2 h1:kmbcoWgbzfh5a6rvfjOnfHSGEqD13qu1GfTPRZqg0FI= github.com/aws/aws-sdk-go-v2/service/sqs v1.36.2/go.mod h1:/UPx74a3M0WYeT2yLQYG/qHhkPlPXd6TsppfGgy2COk= github.com/aws/aws-sdk-go-v2/service/sso v1.24.2 h1:bSYXVyUzoTHoKalBmwaZxs97HU9DWWI3ehHSAMa7xOk= github.com/aws/aws-sdk-go-v2/service/sso v1.24.2/go.mod h1:skMqY7JElusiOUjMJMOv1jJsP7YUg7DrhgqZZWuzu1U= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.2 h1:AhmO1fHINP9vFYUE0LHzCWg/LfUWUF+zFPEcY9QXb7o= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.2/go.mod h1:o8aQygT2+MVP0NaV6kbdE1YnnIM8RRVQzoeUH45GOdI= github.com/aws/aws-sdk-go-v2/service/sts v1.32.2 h1:CiS7i0+FUe+/YY1GvIBLLrR/XNGZ4CtM1Ll0XavNuVo= github.com/aws/aws-sdk-go-v2/service/sts v1.32.2/go.mod h1:HtaiBI8CjYoNVde8arShXb94UbQQi9L4EMr6D+xGBwo= github.com/aws/smithy-go v1.22.0 h1:uunKnWlcoL3zO7q+gG2Pk53joueEOsnNB28QdMsmiMM= github.com/aws/smithy-go v1.22.0/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0 h1:UGZ1QwZWY67Z6BmckTU+9Rxn04m2bD3gD6Mk0OIOCPk= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0/go.mod h1:fcwWuDuaObkkChiDlhEpSq9+X1C0omv+s5mBtToAQ64= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= main.go000066400000000000000000000041101470323427300371470ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/example// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package main import ( "context" "fmt" "github.com/aws/aws-sdk-go-v2/aws" awsConfig "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/service/dynamodb" "github.com/aws/aws-sdk-go-v2/service/s3" "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws" "go.opentelemetry.io/otel" stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" ) var tp *sdktrace.TracerProvider func initTracer() { var err error exp, err := stdout.New(stdout.WithPrettyPrint()) if err != nil { fmt.Printf("failed to initialize stdout exporter %v\n", err) return } bsp := sdktrace.NewBatchSpanProcessor(exp) tp = sdktrace.NewTracerProvider( sdktrace.WithSpanProcessor(bsp), ) otel.SetTracerProvider(tp) } func main() { initTracer() // Create a named tracer with package path as its name. tracer := tp.Tracer("example/aws/main") ctx := context.Background() defer func() { _ = tp.Shutdown(ctx) }() var span trace.Span ctx, span = tracer.Start(ctx, "AWS Example") defer span.End() // init aws config cfg, err := awsConfig.LoadDefaultConfig(ctx) if err != nil { panic("configuration error, " + err.Error()) } // instrument all aws clients otelaws.AppendMiddlewares(&cfg.APIOptions) // S3 s3Client := s3.NewFromConfig(cfg) input := &s3.ListBucketsInput{} result, err := s3Client.ListBuckets(ctx, input) if err != nil { fmt.Printf("Got an error retrieving buckets, %v", err) return } fmt.Println("Buckets:") for _, bucket := range result.Buckets { fmt.Println(*bucket.Name + ": " + bucket.CreationDate.Format("2006-01-02 15:04:05 Monday")) } // DynamoDb dynamoDbClient := dynamodb.NewFromConfig(cfg) resp, err := dynamoDbClient.ListTables(ctx, &dynamodb.ListTablesInput{ Limit: aws.Int32(5), }) if err != nil { fmt.Printf("failed to list tables, %v", err) return } fmt.Println("Tables:") for _, tableName := range resp.TableNames { fmt.Println(tableName) } } go.mod000066400000000000000000000020331470323427300353510ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-sdk-go-v2/otelawsmodule go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws go 1.22 require ( github.com/aws/aws-sdk-go-v2 v1.32.2 github.com/aws/aws-sdk-go-v2/service/dynamodb v1.36.2 github.com/aws/aws-sdk-go-v2/service/sqs v1.36.2 github.com/aws/smithy-go v1.22.0 github.com/stretchr/testify v1.9.0 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/trace v1.31.0 ) require ( github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.21 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.21 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0 // indirect github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) go.sum000066400000000000000000000104761470323427300354100ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-sdk-go-v2/otelawsgithub.com/aws/aws-sdk-go-v2 v1.32.2 h1:AkNLZEyYMLnx/Q/mSKkcMqwNFXMAvFto9bNsHqcTduI= github.com/aws/aws-sdk-go-v2 v1.32.2/go.mod h1:2SK5n0a2karNTv5tbP1SjsX0uhttou00v/HpXKM1ZUo= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.21 h1:UAsR3xA31QGf79WzpG/ixT9FZvQlh5HY1NRqSHBNOCk= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.21/go.mod h1:JNr43NFf5L9YaG3eKTm7HQzls9J+A9YYcGI5Quh1r2Y= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.21 h1:6jZVETqmYCadGFvrYEQfC5fAQmlo80CeL5psbno6r0s= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.21/go.mod h1:1SR0GbLlnN3QUmYaflZNiH1ql+1qrSiB2vwcJ+4UM60= github.com/aws/aws-sdk-go-v2/service/dynamodb v1.36.2 h1:kJqyYcGqhWFmXqjRrtFFD4Oc9FXiskhsll2xnlpe8Do= github.com/aws/aws-sdk-go-v2/service/dynamodb v1.36.2/go.mod h1:+t2Zc5VNOzhaWzpGE+cEYZADsgAAQT5v55AO+fhU+2s= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0 h1:TToQNkvGguu209puTojY/ozlqy2d/SFNcoLIqTFi42g= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0/go.mod h1:0jp+ltwkf+SwG2fm/PKo8t4y8pJSgOCO4D8Lz3k0aHQ= github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.2 h1:1G7TTQNPNv5fhCyIQGYk8FOggLgkzKq6c4Y1nOGzAOE= github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.2/go.mod h1:+ybYGLXoF7bcD7wIcMcklxyABZQmuBf1cHUhvY6FGIo= github.com/aws/aws-sdk-go-v2/service/sqs v1.36.2 h1:kmbcoWgbzfh5a6rvfjOnfHSGEqD13qu1GfTPRZqg0FI= github.com/aws/aws-sdk-go-v2/service/sqs v1.36.2/go.mod h1:/UPx74a3M0WYeT2yLQYG/qHhkPlPXd6TsppfGgy2COk= github.com/aws/smithy-go v1.22.0 h1:uunKnWlcoL3zO7q+gG2Pk53joueEOsnNB28QdMsmiMM= github.com/aws/smithy-go v1.22.0/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= sqsattributes.go000066400000000000000000000041121470323427300375070ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelaws // import "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws" import ( "context" "github.com/aws/aws-sdk-go-v2/service/sqs" "github.com/aws/smithy-go/middleware" "go.opentelemetry.io/otel/attribute" semconv "go.opentelemetry.io/otel/semconv/v1.21.0" ) // SQSAttributeSetter sets SQS specific attributes depending on the SQS operation being performed. func SQSAttributeSetter(ctx context.Context, in middleware.InitializeInput) []attribute.KeyValue { sqsAttributes := []attribute.KeyValue{semconv.MessagingSystem("AmazonSQS")} key := semconv.NetPeerNameKey switch v := in.Parameters.(type) { case *sqs.DeleteMessageBatchInput: sqsAttributes = append(sqsAttributes, key.String(*v.QueueUrl)) case *sqs.DeleteMessageInput: sqsAttributes = append(sqsAttributes, key.String(*v.QueueUrl)) case *sqs.DeleteQueueInput: sqsAttributes = append(sqsAttributes, key.String(*v.QueueUrl)) case *sqs.GetQueueAttributesInput: sqsAttributes = append(sqsAttributes, key.String(*v.QueueUrl)) case *sqs.ListDeadLetterSourceQueuesInput: sqsAttributes = append(sqsAttributes, key.String(*v.QueueUrl)) case *sqs.ListQueueTagsInput: sqsAttributes = append(sqsAttributes, key.String(*v.QueueUrl)) case *sqs.PurgeQueueInput: sqsAttributes = append(sqsAttributes, key.String(*v.QueueUrl)) case *sqs.ReceiveMessageInput: sqsAttributes = append(sqsAttributes, key.String(*v.QueueUrl)) case *sqs.RemovePermissionInput: sqsAttributes = append(sqsAttributes, key.String(*v.QueueUrl)) case *sqs.SendMessageBatchInput: sqsAttributes = append(sqsAttributes, key.String(*v.QueueUrl)) case *sqs.SendMessageInput: sqsAttributes = append(sqsAttributes, key.String(*v.QueueUrl)) case *sqs.SetQueueAttributesInput: sqsAttributes = append(sqsAttributes, key.String(*v.QueueUrl)) case *sqs.TagQueueInput: sqsAttributes = append(sqsAttributes, key.String(*v.QueueUrl)) case *sqs.UntagQueueInput: sqsAttributes = append(sqsAttributes, key.String(*v.QueueUrl)) } return sqsAttributes } sqsattributes_test.go000066400000000000000000000111241470323427300405470ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelaws import ( "context" "testing" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/sqs" "github.com/aws/smithy-go/middleware" "github.com/stretchr/testify/assert" semconv "go.opentelemetry.io/otel/semconv/v1.21.0" ) func TestSQSDeleteMessageBatchInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &sqs.DeleteMessageBatchInput{ QueueUrl: aws.String("test-queue-url"), }, } attributes := SQSAttributeSetter(context.TODO(), input) assert.Contains(t, attributes, semconv.NetPeerName("test-queue-url")) } func TestSQSDeleteMessageInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &sqs.DeleteMessageInput{ QueueUrl: aws.String("test-queue-url"), }, } attributes := SQSAttributeSetter(context.TODO(), input) assert.Contains(t, attributes, semconv.NetPeerName("test-queue-url")) } func TestSQSDeleteQueueInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &sqs.DeleteQueueInput{ QueueUrl: aws.String("test-queue-url"), }, } attributes := SQSAttributeSetter(context.TODO(), input) assert.Contains(t, attributes, semconv.NetPeerName("test-queue-url")) } func TestSQSGetQueueAttributesInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &sqs.GetQueueAttributesInput{ QueueUrl: aws.String("test-queue-url"), }, } attributes := SQSAttributeSetter(context.TODO(), input) assert.Contains(t, attributes, semconv.NetPeerName("test-queue-url")) } func TestSQSListDeadLetterSourceQueuesInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &sqs.ListDeadLetterSourceQueuesInput{ QueueUrl: aws.String("test-queue-url"), }, } attributes := SQSAttributeSetter(context.TODO(), input) assert.Contains(t, attributes, semconv.NetPeerName("test-queue-url")) } func TestSQSListQueueTagsInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &sqs.ListQueueTagsInput{ QueueUrl: aws.String("test-queue-url"), }, } attributes := SQSAttributeSetter(context.TODO(), input) assert.Contains(t, attributes, semconv.NetPeerName("test-queue-url")) } func TestSQSPurgeQueueInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &sqs.PurgeQueueInput{ QueueUrl: aws.String("test-queue-url"), }, } attributes := SQSAttributeSetter(context.TODO(), input) assert.Contains(t, attributes, semconv.NetPeerName("test-queue-url")) } func TestSQSReceiveMessageInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &sqs.ReceiveMessageInput{ QueueUrl: aws.String("test-queue-url"), }, } attributes := SQSAttributeSetter(context.TODO(), input) assert.Contains(t, attributes, semconv.NetPeerName("test-queue-url")) } func TestSQSRemovePermissionInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &sqs.RemovePermissionInput{ QueueUrl: aws.String("test-queue-url"), }, } attributes := SQSAttributeSetter(context.TODO(), input) assert.Contains(t, attributes, semconv.NetPeerName("test-queue-url")) } func TestSQSSendMessageBatchInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &sqs.SendMessageBatchInput{ QueueUrl: aws.String("test-queue-url"), }, } attributes := SQSAttributeSetter(context.TODO(), input) assert.Contains(t, attributes, semconv.NetPeerName("test-queue-url")) } func TestSQSSendMessageInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &sqs.SendMessageInput{ QueueUrl: aws.String("test-queue-url"), }, } attributes := SQSAttributeSetter(context.TODO(), input) assert.Contains(t, attributes, semconv.NetPeerName("test-queue-url")) } func TestSQSSetQueueAttributesInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &sqs.SetQueueAttributesInput{ QueueUrl: aws.String("test-queue-url"), }, } attributes := SQSAttributeSetter(context.TODO(), input) assert.Contains(t, attributes, semconv.NetPeerName("test-queue-url")) } func TestSQSTagQueueInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &sqs.TagQueueInput{ QueueUrl: aws.String("test-queue-url"), }, } attributes := SQSAttributeSetter(context.TODO(), input) assert.Contains(t, attributes, semconv.NetPeerName("test-queue-url")) } func TestSQSUntagQueueInput(t *testing.T) { input := middleware.InitializeInput{ Parameters: &sqs.UntagQueueInput{ QueueUrl: aws.String("test-queue-url"), }, } attributes := SQSAttributeSetter(context.TODO(), input) assert.Contains(t, attributes, semconv.NetPeerName("test-queue-url")) } test/000077500000000000000000000000001470323427300352245ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-sdk-go-v2/otelawsaws_test.go000066400000000000000000000115301470323427300374040ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/test// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package test import ( "context" "net/http" "net/http/httptest" "testing" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/route53" "github.com/aws/aws-sdk-go-v2/service/route53/types" smithyauth "github.com/aws/smithy-go/auth" smithyhttp "github.com/aws/smithy-go/transport/http" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/sdk/trace/tracetest" "go.opentelemetry.io/otel/trace" ) type route53AuthResolver struct{} func (r *route53AuthResolver) ResolveAuthSchemes(context.Context, *route53.AuthResolverParameters) ([]*smithyauth.Option, error) { return []*smithyauth.Option{ {SchemeID: smithyauth.SchemeIDAnonymous}, }, nil } func TestAppendMiddlewares(t *testing.T) { cases := map[string]struct { responseStatus int responseBody []byte expectedRegion string expectedError codes.Code expectedRequestID string expectedStatusCode int }{ "invalidChangeBatchError": { responseStatus: http.StatusInternalServerError, responseBody: []byte(` Tried to create resource record set duplicate.example.com. type A, but it already exists b25f48e8-84fd-11e6-80d9-574e0c4664cb `), expectedRegion: "us-east-1", expectedError: codes.Error, expectedRequestID: "b25f48e8-84fd-11e6-80d9-574e0c4664cb", expectedStatusCode: http.StatusInternalServerError, }, "standardRestXMLError": { responseStatus: http.StatusNotFound, responseBody: []byte(` Sender MalformedXML 1 validation error detected: Value null at 'route53#ChangeSet' failed to satisfy constraint: Member must not be null 1234567890A `), expectedRegion: "us-west-1", expectedError: codes.Error, expectedRequestID: "1234567890A", expectedStatusCode: http.StatusNotFound, }, "Success response": { responseStatus: http.StatusOK, responseBody: []byte(` mockComment mockID `), expectedRegion: "us-west-2", expectedStatusCode: http.StatusOK, }, } for name, c := range cases { srv := httptest.NewServer(http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(c.responseStatus) _, err := w.Write(c.responseBody) if err != nil { t.Fatal(err) } })) t.Run(name, func(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr)) svc := route53.New(route53.Options{ Region: c.expectedRegion, BaseEndpoint: &srv.URL, AuthSchemeResolver: &route53AuthResolver{}, AuthSchemes: []smithyhttp.AuthScheme{ smithyhttp.NewAnonymousScheme(), }, Retryer: aws.NopRetryer{}, }) _, err := svc.ChangeResourceRecordSets(context.Background(), &route53.ChangeResourceRecordSetsInput{ ChangeBatch: &types.ChangeBatch{ Changes: []types.Change{}, Comment: aws.String("mock"), }, HostedZoneId: aws.String("zone"), }, func(options *route53.Options) { otelaws.AppendMiddlewares( &options.APIOptions, otelaws.WithTracerProvider(provider)) }) if c.expectedError == codes.Unset { assert.NoError(t, err) } else { assert.Error(t, err) } spans := sr.Ended() require.Len(t, spans, 1) span := spans[0] assert.Equal(t, "Route 53.ChangeResourceRecordSets", span.Name()) assert.Equal(t, trace.SpanKindClient, span.SpanKind()) assert.Equal(t, c.expectedError, span.Status().Code) attrs := span.Attributes() assert.Contains(t, attrs, attribute.Int("http.status_code", c.expectedStatusCode)) if c.expectedRequestID != "" { assert.Contains(t, attrs, attribute.String("aws.request_id", c.expectedRequestID)) } assert.Contains(t, attrs, attribute.String("rpc.system", "aws-api")) assert.Contains(t, attrs, attribute.String("rpc.service", "Route 53")) assert.Contains(t, attrs, attribute.String("aws.region", c.expectedRegion)) assert.Contains(t, attrs, attribute.String("rpc.method", "ChangeResourceRecordSets")) }) srv.Close() } } doc.go000066400000000000000000000006741470323427300363270ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/test// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 /* Package test validates the otelaws instrumentation with the default SDK. This package is in a separate module from the instrumentation it tests to isolate the dependency of the default SDK and not impose this as a transitive dependency for users. */ package test // import "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/test" dynamodbattributes_test.go000066400000000000000000000142441470323427300425230ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/test// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package test import ( "context" "net/http" "net/http/httptest" "testing" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/dynamodb" dtypes "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" smithyauth "github.com/aws/smithy-go/auth" "github.com/aws/smithy-go/middleware" smithyhttp "github.com/aws/smithy-go/transport/http" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/sdk/trace/tracetest" "go.opentelemetry.io/otel/trace" ) type dynamoDBAuthResolver struct{} func (r *dynamoDBAuthResolver) ResolveAuthSchemes(context.Context, *dynamodb.AuthResolverParameters) ([]*smithyauth.Option, error) { return []*smithyauth.Option{ {SchemeID: smithyauth.SchemeIDAnonymous}, }, nil } func TestDynamodbTags(t *testing.T) { cases := struct { responseStatus int expectedRegion string expectedStatusCode int expectedError codes.Code }{ responseStatus: http.StatusOK, expectedRegion: "us-west-2", expectedStatusCode: http.StatusOK, } server := httptest.NewServer(http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(cases.responseStatus) })) defer server.Close() t.Run("dynamodb tags", func(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr)) svc := dynamodb.New(dynamodb.Options{ Region: cases.expectedRegion, BaseEndpoint: &server.URL, AuthSchemeResolver: &dynamoDBAuthResolver{}, AuthSchemes: []smithyhttp.AuthScheme{ smithyhttp.NewAnonymousScheme(), }, Retryer: aws.NopRetryer{}, }) _, err := svc.GetItem(context.Background(), &dynamodb.GetItemInput{ TableName: aws.String("table1"), ConsistentRead: aws.Bool(false), ProjectionExpression: aws.String("test"), Key: map[string]dtypes.AttributeValue{ "id": &dtypes.AttributeValueMemberS{Value: "test"}, }, }, func(options *dynamodb.Options) { otelaws.AppendMiddlewares( &options.APIOptions, otelaws.WithAttributeSetter(otelaws.DynamoDBAttributeSetter), otelaws.WithTracerProvider(provider)) }) if cases.expectedError == codes.Unset { assert.NoError(t, err) } else { assert.Error(t, err) } spans := sr.Ended() require.Len(t, spans, 1) span := spans[0] assert.Equal(t, "DynamoDB.GetItem", span.Name()) assert.Equal(t, trace.SpanKindClient, span.SpanKind()) attrs := span.Attributes() assert.Contains(t, attrs, attribute.Int("http.status_code", cases.expectedStatusCode)) assert.Contains(t, attrs, attribute.String("rpc.service", "DynamoDB")) assert.Contains(t, attrs, attribute.String("aws.region", cases.expectedRegion)) assert.Contains(t, attrs, attribute.String("rpc.method", "GetItem")) assert.Contains(t, attrs, attribute.String("rpc.system", "aws-api")) assert.Contains(t, attrs, attribute.StringSlice( "aws.dynamodb.table_names", []string{"table1"}, )) assert.Contains(t, attrs, attribute.String("aws.dynamodb.projection", "test")) assert.Contains(t, attrs, attribute.Bool("aws.dynamodb.consistent_read", false)) }) } func TestDynamodbTagsCustomSetter(t *testing.T) { cases := struct { responseStatus int expectedRegion string expectedStatusCode int expectedError codes.Code }{ responseStatus: http.StatusOK, expectedRegion: "us-west-2", expectedStatusCode: http.StatusOK, } server := httptest.NewServer(http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(cases.responseStatus) })) defer server.Close() t.Run("dynamodb tags", func(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr)) svc := dynamodb.New(dynamodb.Options{ Region: cases.expectedRegion, BaseEndpoint: &server.URL, AuthSchemeResolver: &dynamoDBAuthResolver{}, Retryer: aws.NopRetryer{}, }) mycustomsetter := otelaws.AttributeSetter(func(context.Context, middleware.InitializeInput) []attribute.KeyValue { customAttributes := []attribute.KeyValue{ { Key: "customattribute2key", Value: attribute.StringValue("customattribute2value"), }, { Key: "customattribute1key", Value: attribute.StringValue("customattribute1value"), }, } return customAttributes }) _, err := svc.GetItem(context.Background(), &dynamodb.GetItemInput{ TableName: aws.String("table1"), ConsistentRead: aws.Bool(false), ProjectionExpression: aws.String("test"), Key: map[string]dtypes.AttributeValue{ "id": &dtypes.AttributeValueMemberS{Value: "test"}, }, }, func(options *dynamodb.Options) { otelaws.AppendMiddlewares( &options.APIOptions, otelaws.WithAttributeSetter(otelaws.DynamoDBAttributeSetter, mycustomsetter), otelaws.WithTracerProvider(provider)) }) if cases.expectedError == codes.Unset { assert.NoError(t, err) } else { assert.Error(t, err) } spans := sr.Ended() require.Len(t, spans, 1) span := spans[0] assert.Equal(t, "DynamoDB.GetItem", span.Name()) assert.Equal(t, trace.SpanKindClient, span.SpanKind()) attrs := span.Attributes() assert.Contains(t, attrs, attribute.Int("http.status_code", cases.expectedStatusCode)) assert.Contains(t, attrs, attribute.String("rpc.service", "DynamoDB")) assert.Contains(t, attrs, attribute.String("aws.region", cases.expectedRegion)) assert.Contains(t, attrs, attribute.String("rpc.method", "GetItem")) assert.Contains(t, attrs, attribute.StringSlice( "aws.dynamodb.table_names", []string{"table1"}, )) assert.Contains(t, attrs, attribute.String("aws.dynamodb.projection", "test")) assert.Contains(t, attrs, attribute.Bool("aws.dynamodb.consistent_read", false)) assert.Contains(t, attrs, attribute.String("customattribute2key", "customattribute2value")) assert.Contains(t, attrs, attribute.String("customattribute1key", "customattribute1value")) }) } go.mod000066400000000000000000000026241470323427300363360ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/testmodule go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/test go 1.22 require ( github.com/aws/aws-sdk-go-v2 v1.32.2 github.com/aws/aws-sdk-go-v2/service/dynamodb v1.36.2 github.com/aws/aws-sdk-go-v2/service/route53 v1.45.2 github.com/aws/smithy-go v1.22.0 github.com/stretchr/testify v1.9.0 go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws v0.56.0 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/sdk v1.31.0 go.opentelemetry.io/otel/trace v1.31.0 ) require ( github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.21 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.21 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0 // indirect github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.2 // indirect github.com/aws/aws-sdk-go-v2/service/sqs v1.36.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect golang.org/x/sys v0.26.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) replace go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws => ../ go.sum000066400000000000000000000117741470323427300363710ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/testgithub.com/aws/aws-sdk-go-v2 v1.32.2 h1:AkNLZEyYMLnx/Q/mSKkcMqwNFXMAvFto9bNsHqcTduI= github.com/aws/aws-sdk-go-v2 v1.32.2/go.mod h1:2SK5n0a2karNTv5tbP1SjsX0uhttou00v/HpXKM1ZUo= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.21 h1:UAsR3xA31QGf79WzpG/ixT9FZvQlh5HY1NRqSHBNOCk= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.21/go.mod h1:JNr43NFf5L9YaG3eKTm7HQzls9J+A9YYcGI5Quh1r2Y= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.21 h1:6jZVETqmYCadGFvrYEQfC5fAQmlo80CeL5psbno6r0s= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.21/go.mod h1:1SR0GbLlnN3QUmYaflZNiH1ql+1qrSiB2vwcJ+4UM60= github.com/aws/aws-sdk-go-v2/service/dynamodb v1.36.2 h1:kJqyYcGqhWFmXqjRrtFFD4Oc9FXiskhsll2xnlpe8Do= github.com/aws/aws-sdk-go-v2/service/dynamodb v1.36.2/go.mod h1:+t2Zc5VNOzhaWzpGE+cEYZADsgAAQT5v55AO+fhU+2s= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0 h1:TToQNkvGguu209puTojY/ozlqy2d/SFNcoLIqTFi42g= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0/go.mod h1:0jp+ltwkf+SwG2fm/PKo8t4y8pJSgOCO4D8Lz3k0aHQ= github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.2 h1:1G7TTQNPNv5fhCyIQGYk8FOggLgkzKq6c4Y1nOGzAOE= github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.2/go.mod h1:+ybYGLXoF7bcD7wIcMcklxyABZQmuBf1cHUhvY6FGIo= github.com/aws/aws-sdk-go-v2/service/route53 v1.45.2 h1:P4ElvGTPph12a87YpxPDIqCvVICeYJFV32UMMS/TIPc= github.com/aws/aws-sdk-go-v2/service/route53 v1.45.2/go.mod h1:zLKE53MjadFH0VYrDerAx25brxLYiSg4Vk3C+qPY4BQ= github.com/aws/aws-sdk-go-v2/service/sqs v1.36.2 h1:kmbcoWgbzfh5a6rvfjOnfHSGEqD13qu1GfTPRZqg0FI= github.com/aws/aws-sdk-go-v2/service/sqs v1.36.2/go.mod h1:/UPx74a3M0WYeT2yLQYG/qHhkPlPXd6TsppfGgy2COk= github.com/aws/smithy-go v1.22.0 h1:uunKnWlcoL3zO7q+gG2Pk53joueEOsnNB28QdMsmiMM= github.com/aws/smithy-go v1.22.0/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= version.go000066400000000000000000000010531470323427300372370ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/test// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package test // import "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/test" // Version is the current release version of the AWS instrumentation test module. func Version() string { return "0.56.0" // This string is updated by the pre_release.sh script during release } // SemVersion is the semantic version to be supplied to tracer/meter creation. // // Deprecated: Use [Version] instead. func SemVersion() string { return Version() } version.go000066400000000000000000000010431470323427300362570ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelaws // import "go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws" // Version is the current release version of the AWS SDKv2 instrumentation. func Version() string { return "0.56.0" // This string is updated by the pre_release.sh script during release } // SemVersion is the semantic version to be supplied to tracer/meter creation. // // Deprecated: Use [Version] instead. func SemVersion() string { return Version() } open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/emicklei/000077500000000000000000000000001470323427300313355ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/emicklei/go-restful/000077500000000000000000000000001470323427300334245ustar00rootroot00000000000000otelrestful/000077500000000000000000000000001470323427300357155ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/emicklei/go-restfulconfig.go000066400000000000000000000041171470323427300375140ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/emicklei/go-restful/otelrestful// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelrestful // import "go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful" import ( "net/http" "go.opentelemetry.io/otel/propagation" oteltrace "go.opentelemetry.io/otel/trace" ) // config is used to configure the go-restful middleware. type config struct { TracerProvider oteltrace.TracerProvider Propagators propagation.TextMapPropagator PublicEndpoint bool PublicEndpointFn func(*http.Request) bool } // Option applies a configuration value. type Option interface { apply(*config) } type optionFunc func(*config) func (o optionFunc) apply(c *config) { o(c) } // WithPublicEndpoint configures the Handler to link the span with an incoming // span context. If this option is not provided, then the association is a child // association instead of a link. func WithPublicEndpoint() Option { return optionFunc(func(c *config) { c.PublicEndpoint = true }) } // WithPropagators specifies propagators to use for extracting // information from the HTTP requests. If none are specified, global // ones will be used. func WithPropagators(propagators propagation.TextMapPropagator) Option { return optionFunc(func(cfg *config) { if propagators != nil { cfg.Propagators = propagators } }) } // WithTracerProvider specifies a tracer provider to use for creating a tracer. // If none is specified, the global provider is used. func WithTracerProvider(provider oteltrace.TracerProvider) Option { return optionFunc(func(cfg *config) { if provider != nil { cfg.TracerProvider = provider } }) } // WithPublicEndpointFn runs with every request, and allows conditionally // configuring the Handler to link the span with an incoming span context. If // this option is not provided or returns false, then the association is a // child association instead of a link. // Note: [WithPublicEndpoint] takes precedence over WithPublicEndpointFn. func WithPublicEndpointFn(fn func(*http.Request) bool) Option { return optionFunc(func(c *config) { c.PublicEndpointFn = fn }) } doc.go000066400000000000000000000011351470323427300370110ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/emicklei/go-restful/otelrestful// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package otelrestful instruments github.com/emicklei/go-restful. // // Instrumentation is provided to trace the emicklei/go-restful/v3 // package (https://github.com/emicklei/go-restful). // // Instrumentation of an incoming request is achieved via a go-restful // FilterFunc called `OTelFilterFunc` which may be applied at any one of // - the container level // - webservice level // - route level package otelrestful // import "go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful" example/000077500000000000000000000000001470323427300373505ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/emicklei/go-restful/otelrestfulDockerfile000066400000000000000000000004331470323427300413420ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/emicklei/go-restful/otelrestful/example# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 FROM golang:1.23-alpine AS base COPY . /src/ WORKDIR /src/instrumentation/github.com/emicklei/go-restful/otelrestful/example FROM base AS go-restful-server RUN go install ./server.go CMD ["/go/bin/server"] README.md000066400000000000000000000014031470323427300406250ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/emicklei/go-restful/otelrestful/example# emicklei/go-restful instrumentation example An HTTP server using emicklei/go-restful and instrumentation. The server has a `/users/{id:[0-9]+}` endpoint. The server generates span information to `stdout`. These instructions assume you have [docker-compose](https://docs.docker.com/compose/) installed. Bring up the `go-restful-server` and `go-restful-client` services to run the example: ```sh docker-compose up --detach go-restful-server go-restful-client ``` The `go-restful-client` service sends just one HTTP request to `go-restful-server` and then exits. View the span generated by `go-restful-server` in the logs: ```sh docker-compose logs go-restful-server ``` Shut down the services when you are finished with the example: ```sh docker-compose down ``` docker-compose.yml000066400000000000000000000011001470323427300427750ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/emicklei/go-restful/otelrestful/example# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 version: "3.7" services: go-restful-client: image: golang:1.23-alpine networks: - example command: - "/bin/sh" - "-c" - "wget -O - http://go-restful-server:8080/users/123" depends_on: - go-restful-server go-restful-server: build: dockerfile: $PWD/Dockerfile context: ../../../../../.. ports: - "8080:80" command: - "/bin/sh" - "-c" - "/go/bin/server" networks: - example networks: example: go.mod000066400000000000000000000015351470323427300404620ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/emicklei/go-restful/otelrestful/examplemodule go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful/example go 1.22 replace ( go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful => ../ go.opentelemetry.io/contrib/propagators/b3 => ../../../../../../propagators/b3 ) require ( github.com/emicklei/go-restful/v3 v3.12.1 go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful v0.56.0 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0 go.opentelemetry.io/otel/sdk v1.31.0 go.opentelemetry.io/otel/trace v1.31.0 ) require ( github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect golang.org/x/sys v0.26.0 // indirect ) go.sum000066400000000000000000000052071470323427300405070ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/emicklei/go-restful/otelrestful/examplegithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0 h1:UGZ1QwZWY67Z6BmckTU+9Rxn04m2bD3gD6Mk0OIOCPk= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0/go.mod h1:fcwWuDuaObkkChiDlhEpSq9+X1C0omv+s5mBtToAQ64= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= server.go000066400000000000000000000051561470323427300412140ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/emicklei/go-restful/otelrestful/example// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package main import ( "context" "log" "net/http" "strconv" "github.com/emicklei/go-restful/v3" "go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/propagation" sdktrace "go.opentelemetry.io/otel/sdk/trace" oteltrace "go.opentelemetry.io/otel/trace" ) var tracer oteltrace.Tracer type userResource struct{} func (u userResource) WebService() *restful.WebService { ws := &restful.WebService{} ws.Path("/users"). Consumes(restful.MIME_JSON). Produces(restful.MIME_JSON) ws.Route(ws.GET("/{user-id}").To(u.getUser). Param(ws.PathParameter("user-id", "identifier of the user").DataType("integer").DefaultValue("1")). Writes(user{}). // on the response Returns(http.StatusOK, "OK", user{}). Returns(http.StatusNotFound, "Not Found", nil)) return ws } func main() { tp, err := initTracer() if err != nil { log.Fatal(err) } defer func() { if err := tp.Shutdown(context.Background()); err != nil { log.Printf("Error shutting down tracer provider: %v", err) } }() u := userResource{} // create the Otel filter filter := otelrestful.OTelFilter("my-service") // use it restful.DefaultContainer.Filter(filter) restful.DefaultContainer.Add(u.WebService()) _ = http.ListenAndServe(":8080", nil) //nolint:gosec // Ignoring G114: Use of net/http serve function that has no support for setting timeouts. } func initTracer() (*sdktrace.TracerProvider, error) { exporter, err := stdout.New(stdout.WithPrettyPrint()) if err != nil { return nil, err } tp := sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithBatcher(exporter), ) otel.SetTracerProvider(tp) otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.Baggage{}, propagation.TraceContext{})) tracer = otel.GetTracerProvider().Tracer("go-restful-server", oteltrace.WithInstrumentationVersion("0.1")) return tp, nil } func (u userResource) getUser(req *restful.Request, resp *restful.Response) { uid := req.PathParameter("user-id") _, span := tracer.Start(req.Request.Context(), "getUser", oteltrace.WithAttributes(attribute.String("id", uid))) defer span.End() id, err := strconv.Atoi(uid) if err == nil && id >= 100 { _ = resp.WriteEntity(user{id}) return } _ = resp.WriteErrorString(http.StatusNotFound, "User could not be found.") } type user struct { ID int `json:"id" description:"identifier of the user"` } go.mod000066400000000000000000000012661470323427300370300ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/emicklei/go-restful/otelrestfulmodule go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful go 1.22 replace go.opentelemetry.io/contrib/propagators/b3 => ../../../../../propagators/b3 require ( github.com/emicklei/go-restful/v3 v3.12.1 github.com/stretchr/testify v1.9.0 go.opentelemetry.io/contrib/propagators/b3 v1.31.0 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/trace v1.31.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) go.sum000066400000000000000000000042101470323427300370450ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/emicklei/go-restful/otelrestfulgithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= internal/000077500000000000000000000000001470323427300375315ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/emicklei/go-restful/otelrestfulsemconvutil/000077500000000000000000000000001470323427300421015ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/emicklei/go-restful/otelrestful/internalgen.go000066400000000000000000000013751470323427300432070ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconvutil// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconvutil" // Generate semconvutil package: //go:generate gotmpl --body=../../../../../../../internal/shared/semconvutil/httpconv_test.go.tmpl "--data={}" --out=httpconv_test.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconvutil/httpconv.go.tmpl "--data={}" --out=httpconv.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconvutil/netconv_test.go.tmpl "--data={}" --out=netconv_test.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconvutil/netconv.go.tmpl "--data={}" --out=netconv.go httpconv.go000066400000000000000000000466321470323427300443100ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconvutil// Code created by gotmpl. DO NOT MODIFY. // source: internal/shared/semconvutil/httpconv.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconvutil" import ( "fmt" "net/http" "strings" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" semconv "go.opentelemetry.io/otel/semconv/v1.20.0" ) // HTTPClientResponse returns trace attributes for an HTTP response received by a // client from a server. It will return the following attributes if the related // values are defined in resp: "http.status.code", // "http.response_content_length". // // This does not add all OpenTelemetry required attributes for an HTTP event, // it assumes ClientRequest was used to create the span with a complete set of // attributes. If a complete set of attributes can be generated using the // request contained in resp. For example: // // append(HTTPClientResponse(resp), ClientRequest(resp.Request)...) func HTTPClientResponse(resp *http.Response) []attribute.KeyValue { return hc.ClientResponse(resp) } // HTTPClientRequest returns trace attributes for an HTTP request made by a client. // The following attributes are always returned: "http.url", "http.method", // "net.peer.name". The following attributes are returned if the related values // are defined in req: "net.peer.port", "user_agent.original", // "http.request_content_length". func HTTPClientRequest(req *http.Request) []attribute.KeyValue { return hc.ClientRequest(req) } // HTTPClientRequestMetrics returns metric attributes for an HTTP request made by a client. // The following attributes are always returned: "http.method", "net.peer.name". // The following attributes are returned if the // related values are defined in req: "net.peer.port". func HTTPClientRequestMetrics(req *http.Request) []attribute.KeyValue { return hc.ClientRequestMetrics(req) } // HTTPClientStatus returns a span status code and message for an HTTP status code // value received by a client. func HTTPClientStatus(code int) (codes.Code, string) { return hc.ClientStatus(code) } // HTTPServerRequest returns trace attributes for an HTTP request received by a // server. // // The server must be the primary server name if it is known. For example this // would be the ServerName directive // (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache // server, and the server_name directive // (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an // nginx server. More generically, the primary server name would be the host // header value that matches the default virtual host of an HTTP server. It // should include the host identifier and if a port is used to route to the // server that port identifier should be included as an appropriate port // suffix. // // If the primary server name is not known, server should be an empty string. // The req Host will be used to determine the server instead. // // The following attributes are always returned: "http.method", "http.scheme", // "http.target", "net.host.name". The following attributes are returned if // they related values are defined in req: "net.host.port", "net.sock.peer.addr", // "net.sock.peer.port", "user_agent.original", "http.client_ip". func HTTPServerRequest(server string, req *http.Request) []attribute.KeyValue { return hc.ServerRequest(server, req) } // HTTPServerRequestMetrics returns metric attributes for an HTTP request received by a // server. // // The server must be the primary server name if it is known. For example this // would be the ServerName directive // (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache // server, and the server_name directive // (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an // nginx server. More generically, the primary server name would be the host // header value that matches the default virtual host of an HTTP server. It // should include the host identifier and if a port is used to route to the // server that port identifier should be included as an appropriate port // suffix. // // If the primary server name is not known, server should be an empty string. // The req Host will be used to determine the server instead. // // The following attributes are always returned: "http.method", "http.scheme", // "net.host.name". The following attributes are returned if they related // values are defined in req: "net.host.port". func HTTPServerRequestMetrics(server string, req *http.Request) []attribute.KeyValue { return hc.ServerRequestMetrics(server, req) } // HTTPServerStatus returns a span status code and message for an HTTP status code // value returned by a server. Status codes in the 400-499 range are not // returned as errors. func HTTPServerStatus(code int) (codes.Code, string) { return hc.ServerStatus(code) } // httpConv are the HTTP semantic convention attributes defined for a version // of the OpenTelemetry specification. type httpConv struct { NetConv *netConv HTTPClientIPKey attribute.Key HTTPMethodKey attribute.Key HTTPRequestContentLengthKey attribute.Key HTTPResponseContentLengthKey attribute.Key HTTPRouteKey attribute.Key HTTPSchemeHTTP attribute.KeyValue HTTPSchemeHTTPS attribute.KeyValue HTTPStatusCodeKey attribute.Key HTTPTargetKey attribute.Key HTTPURLKey attribute.Key UserAgentOriginalKey attribute.Key } var hc = &httpConv{ NetConv: nc, HTTPClientIPKey: semconv.HTTPClientIPKey, HTTPMethodKey: semconv.HTTPMethodKey, HTTPRequestContentLengthKey: semconv.HTTPRequestContentLengthKey, HTTPResponseContentLengthKey: semconv.HTTPResponseContentLengthKey, HTTPRouteKey: semconv.HTTPRouteKey, HTTPSchemeHTTP: semconv.HTTPSchemeHTTP, HTTPSchemeHTTPS: semconv.HTTPSchemeHTTPS, HTTPStatusCodeKey: semconv.HTTPStatusCodeKey, HTTPTargetKey: semconv.HTTPTargetKey, HTTPURLKey: semconv.HTTPURLKey, UserAgentOriginalKey: semconv.UserAgentOriginalKey, } // ClientResponse returns attributes for an HTTP response received by a client // from a server. The following attributes are returned if the related values // are defined in resp: "http.status.code", "http.response_content_length". // // This does not add all OpenTelemetry required attributes for an HTTP event, // it assumes ClientRequest was used to create the span with a complete set of // attributes. If a complete set of attributes can be generated using the // request contained in resp. For example: // // append(ClientResponse(resp), ClientRequest(resp.Request)...) func (c *httpConv) ClientResponse(resp *http.Response) []attribute.KeyValue { /* The following semantic conventions are returned if present: http.status_code int http.response_content_length int */ var n int if resp.StatusCode > 0 { n++ } if resp.ContentLength > 0 { n++ } attrs := make([]attribute.KeyValue, 0, n) if resp.StatusCode > 0 { attrs = append(attrs, c.HTTPStatusCodeKey.Int(resp.StatusCode)) } if resp.ContentLength > 0 { attrs = append(attrs, c.HTTPResponseContentLengthKey.Int(int(resp.ContentLength))) } return attrs } // ClientRequest returns attributes for an HTTP request made by a client. The // following attributes are always returned: "http.url", "http.method", // "net.peer.name". The following attributes are returned if the related values // are defined in req: "net.peer.port", "user_agent.original", // "http.request_content_length", "user_agent.original". func (c *httpConv) ClientRequest(req *http.Request) []attribute.KeyValue { /* The following semantic conventions are returned if present: http.method string user_agent.original string http.url string net.peer.name string net.peer.port int http.request_content_length int */ /* The following semantic conventions are not returned: http.status_code This requires the response. See ClientResponse. http.response_content_length This requires the response. See ClientResponse. net.sock.family This requires the socket used. net.sock.peer.addr This requires the socket used. net.sock.peer.name This requires the socket used. net.sock.peer.port This requires the socket used. http.resend_count This is something outside of a single request. net.protocol.name The value is the Request is ignored, and the go client will always use "http". net.protocol.version The value in the Request is ignored, and the go client will always use 1.1 or 2.0. */ n := 3 // URL, peer name, proto, and method. var h string if req.URL != nil { h = req.URL.Host } peer, p := firstHostPort(h, req.Header.Get("Host")) port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", p) if port > 0 { n++ } useragent := req.UserAgent() if useragent != "" { n++ } if req.ContentLength > 0 { n++ } attrs := make([]attribute.KeyValue, 0, n) attrs = append(attrs, c.method(req.Method)) var u string if req.URL != nil { // Remove any username/password info that may be in the URL. userinfo := req.URL.User req.URL.User = nil u = req.URL.String() // Restore any username/password info that was removed. req.URL.User = userinfo } attrs = append(attrs, c.HTTPURLKey.String(u)) attrs = append(attrs, c.NetConv.PeerName(peer)) if port > 0 { attrs = append(attrs, c.NetConv.PeerPort(port)) } if useragent != "" { attrs = append(attrs, c.UserAgentOriginalKey.String(useragent)) } if l := req.ContentLength; l > 0 { attrs = append(attrs, c.HTTPRequestContentLengthKey.Int64(l)) } return attrs } // ClientRequestMetrics returns metric attributes for an HTTP request made by a client. The // following attributes are always returned: "http.method", "net.peer.name". // The following attributes are returned if the related values // are defined in req: "net.peer.port". func (c *httpConv) ClientRequestMetrics(req *http.Request) []attribute.KeyValue { /* The following semantic conventions are returned if present: http.method string net.peer.name string net.peer.port int */ n := 2 // method, peer name. var h string if req.URL != nil { h = req.URL.Host } peer, p := firstHostPort(h, req.Header.Get("Host")) port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", p) if port > 0 { n++ } attrs := make([]attribute.KeyValue, 0, n) attrs = append(attrs, c.method(req.Method), c.NetConv.PeerName(peer)) if port > 0 { attrs = append(attrs, c.NetConv.PeerPort(port)) } return attrs } // ServerRequest returns attributes for an HTTP request received by a server. // // The server must be the primary server name if it is known. For example this // would be the ServerName directive // (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache // server, and the server_name directive // (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an // nginx server. More generically, the primary server name would be the host // header value that matches the default virtual host of an HTTP server. It // should include the host identifier and if a port is used to route to the // server that port identifier should be included as an appropriate port // suffix. // // If the primary server name is not known, server should be an empty string. // The req Host will be used to determine the server instead. // // The following attributes are always returned: "http.method", "http.scheme", // "http.target", "net.host.name". The following attributes are returned if they // related values are defined in req: "net.host.port", "net.sock.peer.addr", // "net.sock.peer.port", "user_agent.original", "http.client_ip", // "net.protocol.name", "net.protocol.version". func (c *httpConv) ServerRequest(server string, req *http.Request) []attribute.KeyValue { /* The following semantic conventions are returned if present: http.method string http.scheme string net.host.name string net.host.port int net.sock.peer.addr string net.sock.peer.port int user_agent.original string http.client_ip string net.protocol.name string Note: not set if the value is "http". net.protocol.version string http.target string Note: doesn't include the query parameter. */ /* The following semantic conventions are not returned: http.status_code This requires the response. http.request_content_length This requires the len() of body, which can mutate it. http.response_content_length This requires the response. http.route This is not available. net.sock.peer.name This would require a DNS lookup. net.sock.host.addr The request doesn't have access to the underlying socket. net.sock.host.port The request doesn't have access to the underlying socket. */ n := 4 // Method, scheme, proto, and host name. var host string var p int if server == "" { host, p = splitHostPort(req.Host) } else { // Prioritize the primary server name. host, p = splitHostPort(server) if p < 0 { _, p = splitHostPort(req.Host) } } hostPort := requiredHTTPPort(req.TLS != nil, p) if hostPort > 0 { n++ } peer, peerPort := splitHostPort(req.RemoteAddr) if peer != "" { n++ if peerPort > 0 { n++ } } useragent := req.UserAgent() if useragent != "" { n++ } clientIP := serverClientIP(req.Header.Get("X-Forwarded-For")) if clientIP != "" { n++ } var target string if req.URL != nil { target = req.URL.Path if target != "" { n++ } } protoName, protoVersion := netProtocol(req.Proto) if protoName != "" && protoName != "http" { n++ } if protoVersion != "" { n++ } attrs := make([]attribute.KeyValue, 0, n) attrs = append(attrs, c.method(req.Method)) attrs = append(attrs, c.scheme(req.TLS != nil)) attrs = append(attrs, c.NetConv.HostName(host)) if hostPort > 0 { attrs = append(attrs, c.NetConv.HostPort(hostPort)) } if peer != "" { // The Go HTTP server sets RemoteAddr to "IP:port", this will not be a // file-path that would be interpreted with a sock family. attrs = append(attrs, c.NetConv.SockPeerAddr(peer)) if peerPort > 0 { attrs = append(attrs, c.NetConv.SockPeerPort(peerPort)) } } if useragent != "" { attrs = append(attrs, c.UserAgentOriginalKey.String(useragent)) } if clientIP != "" { attrs = append(attrs, c.HTTPClientIPKey.String(clientIP)) } if target != "" { attrs = append(attrs, c.HTTPTargetKey.String(target)) } if protoName != "" && protoName != "http" { attrs = append(attrs, c.NetConv.NetProtocolName.String(protoName)) } if protoVersion != "" { attrs = append(attrs, c.NetConv.NetProtocolVersion.String(protoVersion)) } return attrs } // ServerRequestMetrics returns metric attributes for an HTTP request received // by a server. // // The server must be the primary server name if it is known. For example this // would be the ServerName directive // (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache // server, and the server_name directive // (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an // nginx server. More generically, the primary server name would be the host // header value that matches the default virtual host of an HTTP server. It // should include the host identifier and if a port is used to route to the // server that port identifier should be included as an appropriate port // suffix. // // If the primary server name is not known, server should be an empty string. // The req Host will be used to determine the server instead. // // The following attributes are always returned: "http.method", "http.scheme", // "net.host.name". The following attributes are returned if they related // values are defined in req: "net.host.port". func (c *httpConv) ServerRequestMetrics(server string, req *http.Request) []attribute.KeyValue { /* The following semantic conventions are returned if present: http.scheme string http.route string http.method string http.status_code int net.host.name string net.host.port int net.protocol.name string Note: not set if the value is "http". net.protocol.version string */ n := 3 // Method, scheme, and host name. var host string var p int if server == "" { host, p = splitHostPort(req.Host) } else { // Prioritize the primary server name. host, p = splitHostPort(server) if p < 0 { _, p = splitHostPort(req.Host) } } hostPort := requiredHTTPPort(req.TLS != nil, p) if hostPort > 0 { n++ } protoName, protoVersion := netProtocol(req.Proto) if protoName != "" { n++ } if protoVersion != "" { n++ } attrs := make([]attribute.KeyValue, 0, n) attrs = append(attrs, c.methodMetric(req.Method)) attrs = append(attrs, c.scheme(req.TLS != nil)) attrs = append(attrs, c.NetConv.HostName(host)) if hostPort > 0 { attrs = append(attrs, c.NetConv.HostPort(hostPort)) } if protoName != "" { attrs = append(attrs, c.NetConv.NetProtocolName.String(protoName)) } if protoVersion != "" { attrs = append(attrs, c.NetConv.NetProtocolVersion.String(protoVersion)) } return attrs } func (c *httpConv) method(method string) attribute.KeyValue { if method == "" { return c.HTTPMethodKey.String(http.MethodGet) } return c.HTTPMethodKey.String(method) } func (c *httpConv) methodMetric(method string) attribute.KeyValue { method = strings.ToUpper(method) switch method { case http.MethodConnect, http.MethodDelete, http.MethodGet, http.MethodHead, http.MethodOptions, http.MethodPatch, http.MethodPost, http.MethodPut, http.MethodTrace: default: method = "_OTHER" } return c.HTTPMethodKey.String(method) } func (c *httpConv) scheme(https bool) attribute.KeyValue { // nolint:revive if https { return c.HTTPSchemeHTTPS } return c.HTTPSchemeHTTP } func serverClientIP(xForwardedFor string) string { if idx := strings.Index(xForwardedFor, ","); idx >= 0 { xForwardedFor = xForwardedFor[:idx] } return xForwardedFor } func requiredHTTPPort(https bool, port int) int { // nolint:revive if https { if port > 0 && port != 443 { return port } } else { if port > 0 && port != 80 { return port } } return -1 } // Return the request host and port from the first non-empty source. func firstHostPort(source ...string) (host string, port int) { for _, hostport := range source { host, port = splitHostPort(hostport) if host != "" || port > 0 { break } } return } // ClientStatus returns a span status code and message for an HTTP status code // value received by a client. func (c *httpConv) ClientStatus(code int) (codes.Code, string) { if code < 100 || code >= 600 { return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) } if code >= 400 { return codes.Error, "" } return codes.Unset, "" } // ServerStatus returns a span status code and message for an HTTP status code // value returned by a server. Status codes in the 400-499 range are not // returned as errors. func (c *httpConv) ServerStatus(code int) (codes.Code, string) { if code < 100 || code >= 600 { return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) } if code >= 500 { return codes.Error, "" } return codes.Unset, "" } httpconv_test.go000066400000000000000000000372171470323427300453460ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconvutil// Code created by gotmpl. DO NOT MODIFY. // source: internal/shared/semconvutil/httpconv_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconvutil import ( "net/http" "net/http/httptest" "net/url" "strconv" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" ) func TestHTTPClientResponse(t *testing.T) { const stat, n = 201, 397 resp := &http.Response{ StatusCode: stat, ContentLength: n, } got := HTTPClientResponse(resp) assert.Equal(t, 2, cap(got), "slice capacity") assert.ElementsMatch(t, []attribute.KeyValue{ attribute.Key("http.status_code").Int(stat), attribute.Key("http.response_content_length").Int(n), }, got) } func TestHTTPSClientRequest(t *testing.T) { req := &http.Request{ Method: http.MethodGet, URL: &url.URL{ Scheme: "https", Host: "127.0.0.1:443", Path: "/resource", }, Proto: "HTTP/1.0", ProtoMajor: 1, ProtoMinor: 0, } assert.ElementsMatch( t, []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.url", "https://127.0.0.1:443/resource"), attribute.String("net.peer.name", "127.0.0.1"), }, HTTPClientRequest(req), ) } func TestHTTPSClientRequestMetrics(t *testing.T) { req := &http.Request{ Method: http.MethodGet, URL: &url.URL{ Scheme: "https", Host: "127.0.0.1:443", Path: "/resource", }, Proto: "HTTP/1.0", ProtoMajor: 1, ProtoMinor: 0, } assert.ElementsMatch( t, []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("net.peer.name", "127.0.0.1"), }, HTTPClientRequestMetrics(req), ) } func TestHTTPClientRequest(t *testing.T) { const ( user = "alice" n = 128 agent = "Go-http-client/1.1" ) req := &http.Request{ Method: http.MethodGet, URL: &url.URL{ Scheme: "http", Host: "127.0.0.1:8080", Path: "/resource", }, Proto: "HTTP/1.0", ProtoMajor: 1, ProtoMinor: 0, Header: http.Header{ "User-Agent": []string{agent}, }, ContentLength: n, } req.SetBasicAuth(user, "pswrd") assert.ElementsMatch( t, []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.url", "http://127.0.0.1:8080/resource"), attribute.String("net.peer.name", "127.0.0.1"), attribute.Int("net.peer.port", 8080), attribute.String("user_agent.original", agent), attribute.Int("http.request_content_length", n), }, HTTPClientRequest(req), ) } func TestHTTPClientRequestMetrics(t *testing.T) { const ( user = "alice" n = 128 agent = "Go-http-client/1.1" ) req := &http.Request{ Method: http.MethodGet, URL: &url.URL{ Scheme: "http", Host: "127.0.0.1:8080", Path: "/resource", }, Proto: "HTTP/1.0", ProtoMajor: 1, ProtoMinor: 0, Header: http.Header{ "User-Agent": []string{agent}, }, ContentLength: n, } req.SetBasicAuth(user, "pswrd") assert.ElementsMatch( t, []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("net.peer.name", "127.0.0.1"), attribute.Int("net.peer.port", 8080), }, HTTPClientRequestMetrics(req), ) } func TestHTTPClientRequestRequired(t *testing.T) { req := new(http.Request) var got []attribute.KeyValue assert.NotPanics(t, func() { got = HTTPClientRequest(req) }) want := []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.url", ""), attribute.String("net.peer.name", ""), } assert.Equal(t, want, got) } func TestHTTPServerRequest(t *testing.T) { got := make(chan *http.Request, 1) handler := func(w http.ResponseWriter, r *http.Request) { got <- r w.WriteHeader(http.StatusOK) } srv := httptest.NewServer(http.HandlerFunc(handler)) defer srv.Close() srvURL, err := url.Parse(srv.URL) require.NoError(t, err) srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) require.NoError(t, err) resp, err := srv.Client().Get(srv.URL) require.NoError(t, err) require.NoError(t, resp.Body.Close()) req := <-got peer, peerPort := splitHostPort(req.RemoteAddr) const user = "alice" req.SetBasicAuth(user, "pswrd") const clientIP = "127.0.0.5" req.Header.Add("X-Forwarded-For", clientIP) assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.scheme", "http"), attribute.String("net.host.name", srvURL.Hostname()), attribute.Int("net.host.port", int(srvPort)), attribute.String("net.sock.peer.addr", peer), attribute.Int("net.sock.peer.port", peerPort), attribute.String("user_agent.original", "Go-http-client/1.1"), attribute.String("http.client_ip", clientIP), attribute.String("net.protocol.version", "1.1"), attribute.String("http.target", "/"), }, HTTPServerRequest("", req)) } func TestHTTPServerRequestMetrics(t *testing.T) { got := make(chan *http.Request, 1) handler := func(w http.ResponseWriter, r *http.Request) { got <- r w.WriteHeader(http.StatusOK) } srv := httptest.NewServer(http.HandlerFunc(handler)) defer srv.Close() srvURL, err := url.Parse(srv.URL) require.NoError(t, err) srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) require.NoError(t, err) resp, err := srv.Client().Get(srv.URL) require.NoError(t, err) require.NoError(t, resp.Body.Close()) req := <-got assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.scheme", "http"), attribute.String("net.host.name", srvURL.Hostname()), attribute.Int("net.host.port", int(srvPort)), attribute.String("net.protocol.name", "http"), attribute.String("net.protocol.version", "1.1"), }, HTTPServerRequestMetrics("", req)) } func TestHTTPServerName(t *testing.T) { req := new(http.Request) var got []attribute.KeyValue const ( host = "test.semconv.server" port = 8080 ) portStr := strconv.Itoa(port) server := host + ":" + portStr assert.NotPanics(t, func() { got = HTTPServerRequest(server, req) }) assert.Contains(t, got, attribute.String("net.host.name", host)) assert.Contains(t, got, attribute.Int("net.host.port", port)) req = &http.Request{Host: "alt.host.name:" + portStr} // The server parameter does not include a port, ServerRequest should use // the port in the request Host field. assert.NotPanics(t, func() { got = HTTPServerRequest(host, req) }) assert.Contains(t, got, attribute.String("net.host.name", host)) assert.Contains(t, got, attribute.Int("net.host.port", port)) } func TestHTTPServerRequestFailsGracefully(t *testing.T) { req := new(http.Request) var got []attribute.KeyValue assert.NotPanics(t, func() { got = HTTPServerRequest("", req) }) want := []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.scheme", "http"), attribute.String("net.host.name", ""), } assert.ElementsMatch(t, want, got) } func TestHTTPMethod(t *testing.T) { assert.Equal(t, attribute.String("http.method", "POST"), hc.method("POST")) assert.Equal(t, attribute.String("http.method", "GET"), hc.method("")) assert.Equal(t, attribute.String("http.method", "garbage"), hc.method("garbage")) } func TestHTTPScheme(t *testing.T) { assert.Equal(t, attribute.String("http.scheme", "http"), hc.scheme(false)) assert.Equal(t, attribute.String("http.scheme", "https"), hc.scheme(true)) } func TestHTTPServerClientIP(t *testing.T) { tests := []struct { xForwardedFor string want string }{ {"", ""}, {"127.0.0.1", "127.0.0.1"}, {"127.0.0.1,127.0.0.5", "127.0.0.1"}, } for _, test := range tests { got := serverClientIP(test.xForwardedFor) assert.Equal(t, test.want, got, test.xForwardedFor) } } func TestRequiredHTTPPort(t *testing.T) { tests := []struct { https bool port int want int }{ {true, 443, -1}, {true, 80, 80}, {true, 8081, 8081}, {false, 443, 443}, {false, 80, -1}, {false, 8080, 8080}, } for _, test := range tests { got := requiredHTTPPort(test.https, test.port) assert.Equal(t, test.want, got, test.https, test.port) } } func TestFirstHostPort(t *testing.T) { host, port := "127.0.0.1", 8080 hostport := "127.0.0.1:8080" sources := [][]string{ {hostport}, {"", hostport}, {"", "", hostport}, {"", "", hostport, ""}, {"", "", hostport, "127.0.0.3:80"}, } for _, src := range sources { h, p := firstHostPort(src...) assert.Equal(t, host, h, src) assert.Equal(t, port, p, src) } } func TestHTTPClientStatus(t *testing.T) { tests := []struct { code int stat codes.Code msg bool }{ {0, codes.Error, true}, {http.StatusContinue, codes.Unset, false}, {http.StatusSwitchingProtocols, codes.Unset, false}, {http.StatusProcessing, codes.Unset, false}, {http.StatusEarlyHints, codes.Unset, false}, {http.StatusOK, codes.Unset, false}, {http.StatusCreated, codes.Unset, false}, {http.StatusAccepted, codes.Unset, false}, {http.StatusNonAuthoritativeInfo, codes.Unset, false}, {http.StatusNoContent, codes.Unset, false}, {http.StatusResetContent, codes.Unset, false}, {http.StatusPartialContent, codes.Unset, false}, {http.StatusMultiStatus, codes.Unset, false}, {http.StatusAlreadyReported, codes.Unset, false}, {http.StatusIMUsed, codes.Unset, false}, {http.StatusMultipleChoices, codes.Unset, false}, {http.StatusMovedPermanently, codes.Unset, false}, {http.StatusFound, codes.Unset, false}, {http.StatusSeeOther, codes.Unset, false}, {http.StatusNotModified, codes.Unset, false}, {http.StatusUseProxy, codes.Unset, false}, {306, codes.Unset, false}, {http.StatusTemporaryRedirect, codes.Unset, false}, {http.StatusPermanentRedirect, codes.Unset, false}, {http.StatusBadRequest, codes.Error, false}, {http.StatusUnauthorized, codes.Error, false}, {http.StatusPaymentRequired, codes.Error, false}, {http.StatusForbidden, codes.Error, false}, {http.StatusNotFound, codes.Error, false}, {http.StatusMethodNotAllowed, codes.Error, false}, {http.StatusNotAcceptable, codes.Error, false}, {http.StatusProxyAuthRequired, codes.Error, false}, {http.StatusRequestTimeout, codes.Error, false}, {http.StatusConflict, codes.Error, false}, {http.StatusGone, codes.Error, false}, {http.StatusLengthRequired, codes.Error, false}, {http.StatusPreconditionFailed, codes.Error, false}, {http.StatusRequestEntityTooLarge, codes.Error, false}, {http.StatusRequestURITooLong, codes.Error, false}, {http.StatusUnsupportedMediaType, codes.Error, false}, {http.StatusRequestedRangeNotSatisfiable, codes.Error, false}, {http.StatusExpectationFailed, codes.Error, false}, {http.StatusTeapot, codes.Error, false}, {http.StatusMisdirectedRequest, codes.Error, false}, {http.StatusUnprocessableEntity, codes.Error, false}, {http.StatusLocked, codes.Error, false}, {http.StatusFailedDependency, codes.Error, false}, {http.StatusTooEarly, codes.Error, false}, {http.StatusUpgradeRequired, codes.Error, false}, {http.StatusPreconditionRequired, codes.Error, false}, {http.StatusTooManyRequests, codes.Error, false}, {http.StatusRequestHeaderFieldsTooLarge, codes.Error, false}, {http.StatusUnavailableForLegalReasons, codes.Error, false}, {499, codes.Error, false}, {http.StatusInternalServerError, codes.Error, false}, {http.StatusNotImplemented, codes.Error, false}, {http.StatusBadGateway, codes.Error, false}, {http.StatusServiceUnavailable, codes.Error, false}, {http.StatusGatewayTimeout, codes.Error, false}, {http.StatusHTTPVersionNotSupported, codes.Error, false}, {http.StatusVariantAlsoNegotiates, codes.Error, false}, {http.StatusInsufficientStorage, codes.Error, false}, {http.StatusLoopDetected, codes.Error, false}, {http.StatusNotExtended, codes.Error, false}, {http.StatusNetworkAuthenticationRequired, codes.Error, false}, {600, codes.Error, true}, } for _, test := range tests { t.Run(strconv.Itoa(test.code), func(t *testing.T) { c, msg := HTTPClientStatus(test.code) assert.Equal(t, test.stat, c) if test.msg && msg == "" { t.Errorf("expected non-empty message for %d", test.code) } else if !test.msg && msg != "" { t.Errorf("expected empty message for %d, got: %s", test.code, msg) } }) } } func TestHTTPServerStatus(t *testing.T) { tests := []struct { code int stat codes.Code msg bool }{ {0, codes.Error, true}, {http.StatusContinue, codes.Unset, false}, {http.StatusSwitchingProtocols, codes.Unset, false}, {http.StatusProcessing, codes.Unset, false}, {http.StatusEarlyHints, codes.Unset, false}, {http.StatusOK, codes.Unset, false}, {http.StatusCreated, codes.Unset, false}, {http.StatusAccepted, codes.Unset, false}, {http.StatusNonAuthoritativeInfo, codes.Unset, false}, {http.StatusNoContent, codes.Unset, false}, {http.StatusResetContent, codes.Unset, false}, {http.StatusPartialContent, codes.Unset, false}, {http.StatusMultiStatus, codes.Unset, false}, {http.StatusAlreadyReported, codes.Unset, false}, {http.StatusIMUsed, codes.Unset, false}, {http.StatusMultipleChoices, codes.Unset, false}, {http.StatusMovedPermanently, codes.Unset, false}, {http.StatusFound, codes.Unset, false}, {http.StatusSeeOther, codes.Unset, false}, {http.StatusNotModified, codes.Unset, false}, {http.StatusUseProxy, codes.Unset, false}, {306, codes.Unset, false}, {http.StatusTemporaryRedirect, codes.Unset, false}, {http.StatusPermanentRedirect, codes.Unset, false}, {http.StatusBadRequest, codes.Unset, false}, {http.StatusUnauthorized, codes.Unset, false}, {http.StatusPaymentRequired, codes.Unset, false}, {http.StatusForbidden, codes.Unset, false}, {http.StatusNotFound, codes.Unset, false}, {http.StatusMethodNotAllowed, codes.Unset, false}, {http.StatusNotAcceptable, codes.Unset, false}, {http.StatusProxyAuthRequired, codes.Unset, false}, {http.StatusRequestTimeout, codes.Unset, false}, {http.StatusConflict, codes.Unset, false}, {http.StatusGone, codes.Unset, false}, {http.StatusLengthRequired, codes.Unset, false}, {http.StatusPreconditionFailed, codes.Unset, false}, {http.StatusRequestEntityTooLarge, codes.Unset, false}, {http.StatusRequestURITooLong, codes.Unset, false}, {http.StatusUnsupportedMediaType, codes.Unset, false}, {http.StatusRequestedRangeNotSatisfiable, codes.Unset, false}, {http.StatusExpectationFailed, codes.Unset, false}, {http.StatusTeapot, codes.Unset, false}, {http.StatusMisdirectedRequest, codes.Unset, false}, {http.StatusUnprocessableEntity, codes.Unset, false}, {http.StatusLocked, codes.Unset, false}, {http.StatusFailedDependency, codes.Unset, false}, {http.StatusTooEarly, codes.Unset, false}, {http.StatusUpgradeRequired, codes.Unset, false}, {http.StatusPreconditionRequired, codes.Unset, false}, {http.StatusTooManyRequests, codes.Unset, false}, {http.StatusRequestHeaderFieldsTooLarge, codes.Unset, false}, {http.StatusUnavailableForLegalReasons, codes.Unset, false}, {499, codes.Unset, false}, {http.StatusInternalServerError, codes.Error, false}, {http.StatusNotImplemented, codes.Error, false}, {http.StatusBadGateway, codes.Error, false}, {http.StatusServiceUnavailable, codes.Error, false}, {http.StatusGatewayTimeout, codes.Error, false}, {http.StatusHTTPVersionNotSupported, codes.Error, false}, {http.StatusVariantAlsoNegotiates, codes.Error, false}, {http.StatusInsufficientStorage, codes.Error, false}, {http.StatusLoopDetected, codes.Error, false}, {http.StatusNotExtended, codes.Error, false}, {http.StatusNetworkAuthenticationRequired, codes.Error, false}, {600, codes.Error, true}, } for _, test := range tests { c, msg := HTTPServerStatus(test.code) assert.Equal(t, test.stat, c) if test.msg && msg == "" { t.Errorf("expected non-empty message for %d", test.code) } else if !test.msg && msg != "" { t.Errorf("expected empty message for %d, got: %s", test.code, msg) } } } netconv.go000066400000000000000000000123001470323427300441000ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconvutil// Code created by gotmpl. DO NOT MODIFY. // source: internal/shared/semconvutil/netconv.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconvutil" import ( "net" "strconv" "strings" "go.opentelemetry.io/otel/attribute" semconv "go.opentelemetry.io/otel/semconv/v1.20.0" ) // NetTransport returns a trace attribute describing the transport protocol of the // passed network. See the net.Dial for information about acceptable network // values. func NetTransport(network string) attribute.KeyValue { return nc.Transport(network) } // netConv are the network semantic convention attributes defined for a version // of the OpenTelemetry specification. type netConv struct { NetHostNameKey attribute.Key NetHostPortKey attribute.Key NetPeerNameKey attribute.Key NetPeerPortKey attribute.Key NetProtocolName attribute.Key NetProtocolVersion attribute.Key NetSockFamilyKey attribute.Key NetSockPeerAddrKey attribute.Key NetSockPeerPortKey attribute.Key NetSockHostAddrKey attribute.Key NetSockHostPortKey attribute.Key NetTransportOther attribute.KeyValue NetTransportTCP attribute.KeyValue NetTransportUDP attribute.KeyValue NetTransportInProc attribute.KeyValue } var nc = &netConv{ NetHostNameKey: semconv.NetHostNameKey, NetHostPortKey: semconv.NetHostPortKey, NetPeerNameKey: semconv.NetPeerNameKey, NetPeerPortKey: semconv.NetPeerPortKey, NetProtocolName: semconv.NetProtocolNameKey, NetProtocolVersion: semconv.NetProtocolVersionKey, NetSockFamilyKey: semconv.NetSockFamilyKey, NetSockPeerAddrKey: semconv.NetSockPeerAddrKey, NetSockPeerPortKey: semconv.NetSockPeerPortKey, NetSockHostAddrKey: semconv.NetSockHostAddrKey, NetSockHostPortKey: semconv.NetSockHostPortKey, NetTransportOther: semconv.NetTransportOther, NetTransportTCP: semconv.NetTransportTCP, NetTransportUDP: semconv.NetTransportUDP, NetTransportInProc: semconv.NetTransportInProc, } func (c *netConv) Transport(network string) attribute.KeyValue { switch network { case "tcp", "tcp4", "tcp6": return c.NetTransportTCP case "udp", "udp4", "udp6": return c.NetTransportUDP case "unix", "unixgram", "unixpacket": return c.NetTransportInProc default: // "ip:*", "ip4:*", and "ip6:*" all are considered other. return c.NetTransportOther } } // Host returns attributes for a network host address. func (c *netConv) Host(address string) []attribute.KeyValue { h, p := splitHostPort(address) var n int if h != "" { n++ if p > 0 { n++ } } if n == 0 { return nil } attrs := make([]attribute.KeyValue, 0, n) attrs = append(attrs, c.HostName(h)) if p > 0 { attrs = append(attrs, c.HostPort(p)) } return attrs } func (c *netConv) HostName(name string) attribute.KeyValue { return c.NetHostNameKey.String(name) } func (c *netConv) HostPort(port int) attribute.KeyValue { return c.NetHostPortKey.Int(port) } func family(network, address string) string { switch network { case "unix", "unixgram", "unixpacket": return "unix" default: if ip := net.ParseIP(address); ip != nil { if ip.To4() == nil { return "inet6" } return "inet" } } return "" } // Peer returns attributes for a network peer address. func (c *netConv) Peer(address string) []attribute.KeyValue { h, p := splitHostPort(address) var n int if h != "" { n++ if p > 0 { n++ } } if n == 0 { return nil } attrs := make([]attribute.KeyValue, 0, n) attrs = append(attrs, c.PeerName(h)) if p > 0 { attrs = append(attrs, c.PeerPort(p)) } return attrs } func (c *netConv) PeerName(name string) attribute.KeyValue { return c.NetPeerNameKey.String(name) } func (c *netConv) PeerPort(port int) attribute.KeyValue { return c.NetPeerPortKey.Int(port) } func (c *netConv) SockPeerAddr(addr string) attribute.KeyValue { return c.NetSockPeerAddrKey.String(addr) } func (c *netConv) SockPeerPort(port int) attribute.KeyValue { return c.NetSockPeerPortKey.Int(port) } // splitHostPort splits a network address hostport of the form "host", // "host%zone", "[host]", "[host%zone], "host:port", "host%zone:port", // "[host]:port", "[host%zone]:port", or ":port" into host or host%zone and // port. // // An empty host is returned if it is not provided or unparsable. A negative // port is returned if it is not provided or unparsable. func splitHostPort(hostport string) (host string, port int) { port = -1 if strings.HasPrefix(hostport, "[") { addrEnd := strings.LastIndex(hostport, "]") if addrEnd < 0 { // Invalid hostport. return } if i := strings.LastIndex(hostport[addrEnd:], ":"); i < 0 { host = hostport[1:addrEnd] return } } else { if i := strings.LastIndex(hostport, ":"); i < 0 { host = hostport return } } host, pStr, err := net.SplitHostPort(hostport) if err != nil { return } p, err := strconv.ParseUint(pStr, 10, 16) if err != nil { return } return host, int(p) // nolint: gosec // Bitsize checked to be 16 above. } func netProtocol(proto string) (name string, version string) { name, version, _ = strings.Cut(proto, "/") name = strings.ToLower(name) return name, version } netconv_test.go000066400000000000000000000130111470323427300451370ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconvutil// Code created by gotmpl. DO NOT MODIFY. // source: internal/shared/semconvutil/netconv_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconvutil import ( "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/attribute" ) const ( addr = "127.0.0.1" port = 1834 ) func TestNetTransport(t *testing.T) { transports := map[string]attribute.KeyValue{ "tcp": attribute.String("net.transport", "ip_tcp"), "tcp4": attribute.String("net.transport", "ip_tcp"), "tcp6": attribute.String("net.transport", "ip_tcp"), "udp": attribute.String("net.transport", "ip_udp"), "udp4": attribute.String("net.transport", "ip_udp"), "udp6": attribute.String("net.transport", "ip_udp"), "unix": attribute.String("net.transport", "inproc"), "unixgram": attribute.String("net.transport", "inproc"), "unixpacket": attribute.String("net.transport", "inproc"), "ip:1": attribute.String("net.transport", "other"), "ip:icmp": attribute.String("net.transport", "other"), "ip4:proto": attribute.String("net.transport", "other"), "ip6:proto": attribute.String("net.transport", "other"), } for network, want := range transports { assert.Equal(t, want, NetTransport(network)) } } func TestNetHost(t *testing.T) { testAddrs(t, []addrTest{ {address: "", expected: nil}, {address: "192.0.0.1", expected: []attribute.KeyValue{ nc.HostName("192.0.0.1"), }}, {address: "192.0.0.1:9090", expected: []attribute.KeyValue{ nc.HostName("192.0.0.1"), nc.HostPort(9090), }}, }, nc.Host) } func TestNetHostName(t *testing.T) { expected := attribute.Key("net.host.name").String(addr) assert.Equal(t, expected, nc.HostName(addr)) } func TestNetHostPort(t *testing.T) { expected := attribute.Key("net.host.port").Int(port) assert.Equal(t, expected, nc.HostPort(port)) } func TestNetPeer(t *testing.T) { testAddrs(t, []addrTest{ {address: "", expected: nil}, {address: "example.com", expected: []attribute.KeyValue{ nc.PeerName("example.com"), }}, {address: "/tmp/file", expected: []attribute.KeyValue{ nc.PeerName("/tmp/file"), }}, {address: "192.0.0.1", expected: []attribute.KeyValue{ nc.PeerName("192.0.0.1"), }}, {address: ":9090", expected: nil}, {address: "192.0.0.1:9090", expected: []attribute.KeyValue{ nc.PeerName("192.0.0.1"), nc.PeerPort(9090), }}, }, nc.Peer) } func TestNetPeerName(t *testing.T) { expected := attribute.Key("net.peer.name").String(addr) assert.Equal(t, expected, nc.PeerName(addr)) } func TestNetPeerPort(t *testing.T) { expected := attribute.Key("net.peer.port").Int(port) assert.Equal(t, expected, nc.PeerPort(port)) } func TestNetSockPeerName(t *testing.T) { expected := attribute.Key("net.sock.peer.addr").String(addr) assert.Equal(t, expected, nc.SockPeerAddr(addr)) } func TestNetSockPeerPort(t *testing.T) { expected := attribute.Key("net.sock.peer.port").Int(port) assert.Equal(t, expected, nc.SockPeerPort(port)) } func TestNetFamily(t *testing.T) { tests := []struct { network string address string expect string }{ {"", "", ""}, {"unix", "", "unix"}, {"unix", "gibberish", "unix"}, {"unixgram", "", "unix"}, {"unixgram", "gibberish", "unix"}, {"unixpacket", "gibberish", "unix"}, {"tcp", "123.0.2.8", "inet"}, {"tcp", "gibberish", ""}, {"", "123.0.2.8", "inet"}, {"", "gibberish", ""}, {"tcp", "fe80::1", "inet6"}, {"", "fe80::1", "inet6"}, } for _, test := range tests { got := family(test.network, test.address) assert.Equal(t, test.expect, got, test.network+"/"+test.address) } } func TestSplitHostPort(t *testing.T) { tests := []struct { hostport string host string port int }{ {"", "", -1}, {":8080", "", 8080}, {"127.0.0.1", "127.0.0.1", -1}, {"www.example.com", "www.example.com", -1}, {"127.0.0.1%25en0", "127.0.0.1%25en0", -1}, {"[]", "", -1}, // Ensure this doesn't panic. {"[fe80::1", "", -1}, {"[fe80::1]", "fe80::1", -1}, {"[fe80::1%25en0]", "fe80::1%25en0", -1}, {"[fe80::1]:8080", "fe80::1", 8080}, {"[fe80::1]::", "", -1}, // Too many colons. {"127.0.0.1:", "127.0.0.1", -1}, {"127.0.0.1:port", "127.0.0.1", -1}, {"127.0.0.1:8080", "127.0.0.1", 8080}, {"www.example.com:8080", "www.example.com", 8080}, {"127.0.0.1%25en0:8080", "127.0.0.1%25en0", 8080}, } for _, test := range tests { h, p := splitHostPort(test.hostport) assert.Equal(t, test.host, h, test.hostport) assert.Equal(t, test.port, p, test.hostport) } } type addrTest struct { address string expected []attribute.KeyValue } func testAddrs(t *testing.T, tests []addrTest, f func(string) []attribute.KeyValue) { t.Helper() for _, test := range tests { got := f(test.address) assert.Equal(t, cap(test.expected), cap(got), "slice capacity") assert.ElementsMatch(t, test.expected, got, test.address) } } func TestNetProtocol(t *testing.T) { type testCase struct { name, version string } tests := map[string]testCase{ "HTTP/1.0": {name: "http", version: "1.0"}, "HTTP/1.1": {name: "http", version: "1.1"}, "HTTP/2": {name: "http", version: "2"}, "HTTP/3": {name: "http", version: "3"}, "SPDY": {name: "spdy"}, "SPDY/2": {name: "spdy", version: "2"}, "QUIC": {name: "quic"}, "unknown/proto/2": {name: "unknown", version: "proto/2"}, "other": {name: "other"}, } for proto, want := range tests { name, version := netProtocol(proto) assert.Equal(t, want.name, name) assert.Equal(t, want.version, version) } } restful.go000066400000000000000000000051251470323427300377330ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/emicklei/go-restful/otelrestful// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelrestful // import "go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful" import ( "github.com/emicklei/go-restful/v3" "go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconvutil" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/propagation" semconv "go.opentelemetry.io/otel/semconv/v1.20.0" oteltrace "go.opentelemetry.io/otel/trace" ) // ScopeName is the instrumentation scope name. const ScopeName = "go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful" // OTelFilter returns a restful.FilterFunction which will trace an incoming request. // // The service parameter should describe the name of the (virtual) server handling // the request. Options can be applied to configure the tracer and propagators // used for this filter. func OTelFilter(service string, opts ...Option) restful.FilterFunction { cfg := config{} for _, opt := range opts { opt.apply(&cfg) } if cfg.TracerProvider == nil { cfg.TracerProvider = otel.GetTracerProvider() } tracer := cfg.TracerProvider.Tracer( ScopeName, oteltrace.WithInstrumentationVersion(Version()), ) if cfg.Propagators == nil { cfg.Propagators = otel.GetTextMapPropagator() } return func(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) { r := req.Request ctx := cfg.Propagators.Extract(r.Context(), propagation.HeaderCarrier(r.Header)) route := req.SelectedRoutePath() spanName := route opts := []oteltrace.SpanStartOption{ oteltrace.WithAttributes(semconvutil.HTTPServerRequest(service, r)...), oteltrace.WithSpanKind(oteltrace.SpanKindServer), } if route != "" { rAttr := semconv.HTTPRoute(route) opts = append(opts, oteltrace.WithAttributes(rAttr)) } if cfg.PublicEndpoint || (cfg.PublicEndpointFn != nil && cfg.PublicEndpointFn(r.WithContext(ctx))) { opts = append(opts, oteltrace.WithNewRoot()) // Linking incoming span context if any for public endpoint. if s := oteltrace.SpanContextFromContext(ctx); s.IsValid() && s.IsRemote() { opts = append(opts, oteltrace.WithLinks(oteltrace.Link{SpanContext: s})) } } ctx, span := tracer.Start(ctx, spanName, opts...) defer span.End() // pass the span through the request context req.Request = req.Request.WithContext(ctx) chain.ProcessFilter(req, resp) status := resp.StatusCode() span.SetStatus(semconvutil.HTTPServerStatus(status)) if status > 0 { span.SetAttributes(semconv.HTTPStatusCode(status)) } } } restful_test.go000066400000000000000000000066771470323427300410070ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/emicklei/go-restful/otelrestful// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelrestful_test import ( "context" "net/http" "net/http/httptest" "testing" "github.com/emicklei/go-restful/v3" "github.com/stretchr/testify/assert" "go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful" b3prop "go.opentelemetry.io/contrib/propagators/b3" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/propagation" oteltrace "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace/noop" ) const tracerName = "go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful" func TestGetSpanNotInstrumented(t *testing.T) { handlerFunc := func(req *restful.Request, resp *restful.Response) { span := oteltrace.SpanFromContext(req.Request.Context()) ok := !span.SpanContext().IsValid() assert.True(t, ok) resp.WriteHeader(http.StatusOK) } ws := &restful.WebService{} ws.Route(ws.GET("/user/{id}").To(handlerFunc)) container := restful.NewContainer() container.Add(ws) r := httptest.NewRequest("GET", "/user/123", nil) w := httptest.NewRecorder() container.ServeHTTP(w, r) } func TestPropagationWithGlobalPropagators(t *testing.T) { defer func(p propagation.TextMapPropagator) { otel.SetTextMapPropagator(p) }(otel.GetTextMapPropagator()) provider := noop.NewTracerProvider() otel.SetTextMapPropagator(propagation.TraceContext{}) r := httptest.NewRequest("GET", "/user/123", nil) w := httptest.NewRecorder() ctx := context.Background() sc := oteltrace.NewSpanContext(oteltrace.SpanContextConfig{ TraceID: oteltrace.TraceID{0x01}, SpanID: oteltrace.SpanID{0x01}, }) ctx = oteltrace.ContextWithRemoteSpanContext(ctx, sc) ctx, _ = provider.Tracer(tracerName).Start(ctx, "test") otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(r.Header)) handlerFunc := func(req *restful.Request, resp *restful.Response) { span := oteltrace.SpanFromContext(req.Request.Context()) assert.Equal(t, sc.TraceID(), span.SpanContext().TraceID()) assert.Equal(t, sc.SpanID(), span.SpanContext().SpanID()) w.WriteHeader(http.StatusOK) } ws := &restful.WebService{} ws.Route(ws.GET("/user/{id}").To(handlerFunc)) container := restful.NewContainer() container.Filter(otelrestful.OTelFilter("foobar", otelrestful.WithTracerProvider(provider))) container.Add(ws) container.ServeHTTP(w, r) } func TestPropagationWithCustomPropagators(t *testing.T) { provider := noop.NewTracerProvider() b3 := b3prop.New() r := httptest.NewRequest("GET", "/user/123", nil) w := httptest.NewRecorder() ctx := context.Background() sc := oteltrace.NewSpanContext(oteltrace.SpanContextConfig{ TraceID: oteltrace.TraceID{0x01}, SpanID: oteltrace.SpanID{0x01}, }) ctx = oteltrace.ContextWithRemoteSpanContext(ctx, sc) ctx, _ = provider.Tracer(tracerName).Start(ctx, "test") b3.Inject(ctx, propagation.HeaderCarrier(r.Header)) handlerFunc := func(req *restful.Request, resp *restful.Response) { span := oteltrace.SpanFromContext(req.Request.Context()) assert.Equal(t, sc.TraceID(), span.SpanContext().TraceID()) assert.Equal(t, sc.SpanID(), span.SpanContext().SpanID()) w.WriteHeader(http.StatusOK) } ws := &restful.WebService{} ws.Route(ws.GET("/user/{id}").To(handlerFunc)) container := restful.NewContainer() container.Filter(otelrestful.OTelFilter("foobar", otelrestful.WithTracerProvider(provider), otelrestful.WithPropagators(b3))) container.Add(ws) container.ServeHTTP(w, r) } test/000077500000000000000000000000001470323427300366745ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/emicklei/go-restful/otelrestfuldoc.go000066400000000000000000000007061470323427300377730ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/emicklei/go-restful/otelrestful/test// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 /* Package test validates the otelrestful instrumentation with the default SDK. This package is in a separate module from the instrumentation it tests to isolate the dependency of the default SDK and not impose this as a transitive dependency for users. */ package test // import "go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful/test" go.mod000066400000000000000000000017051470323427300400050ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/emicklei/go-restful/otelrestful/testmodule go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful/test go 1.22 require ( github.com/emicklei/go-restful/v3 v3.12.1 github.com/stretchr/testify v1.9.0 go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful v0.56.0 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/sdk v1.31.0 go.opentelemetry.io/otel/trace v1.31.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect golang.org/x/sys v0.26.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) replace ( go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful => ../ go.opentelemetry.io/contrib/propagators/b3 => ../../../../../../propagators/b3 ) go.sum000066400000000000000000000051651470323427300400360ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/emicklei/go-restful/otelrestful/testgithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= restful_test.go000066400000000000000000000255351470323427300417600ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/emicklei/go-restful/otelrestful/test// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package test import ( "context" "net/http" "net/http/httptest" "strconv" "testing" "github.com/emicklei/go-restful/v3" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/propagation" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/sdk/trace/tracetest" oteltrace "go.opentelemetry.io/otel/trace" ) func TestChildSpanFromGlobalTracer(t *testing.T) { sr := tracetest.NewSpanRecorder() otel.SetTracerProvider(sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr))) handlerFunc := func(req *restful.Request, resp *restful.Response) { resp.WriteHeader(http.StatusOK) } ws := &restful.WebService{} ws.Route(ws.GET("/user/{id}").To(handlerFunc). Returns(http.StatusOK, "OK", nil). Returns(http.StatusNotFound, "Not Found", nil)) container := restful.NewContainer() container.Filter(otelrestful.OTelFilter("my-service")) container.Add(ws) r := httptest.NewRequest("GET", "/user/123", nil) w := httptest.NewRecorder() container.ServeHTTP(w, r) assert.Len(t, sr.Ended(), 1) } func TestChildSpanFromCustomTracer(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr)) handlerFunc := func(req *restful.Request, resp *restful.Response) { resp.WriteHeader(http.StatusOK) } ws := &restful.WebService{} ws.Route(ws.GET("/user/{id}").To(handlerFunc)) container := restful.NewContainer() container.Filter(otelrestful.OTelFilter("my-service", otelrestful.WithTracerProvider(provider))) container.Add(ws) r := httptest.NewRequest("GET", "/user/123", nil) w := httptest.NewRecorder() container.ServeHTTP(w, r) assert.Len(t, sr.Ended(), 1) } func TestChildSpanNames(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr)) handlerFunc := func(req *restful.Request, resp *restful.Response) { resp.WriteHeader(http.StatusOK) } ws := &restful.WebService{} ws.Route(ws.GET("/user/{id:[0-9]+}").To(handlerFunc)) container := restful.NewContainer() container.Filter(otelrestful.OTelFilter("foobar", otelrestful.WithTracerProvider(provider))) container.Add(ws) ws.Route(ws.GET("/book/{title}").To(func(req *restful.Request, resp *restful.Response) { _, _ = resp.Write(([]byte)("ok")) })) r := httptest.NewRequest("GET", "/user/123", nil) w := httptest.NewRecorder() container.ServeHTTP(w, r) spans := sr.Ended() require.Len(t, spans, 1) assertSpan( t, spans[0], "/user/{id:[0-9]+}", attribute.String("net.host.name", "foobar"), attribute.Int("http.status_code", http.StatusOK), attribute.String("http.method", "GET"), attribute.String("http.route", "/user/{id:[0-9]+}"), ) r = httptest.NewRequest("GET", "/book/foo", nil) w = httptest.NewRecorder() container.ServeHTTP(w, r) spans = sr.Ended() require.Len(t, spans, 2) assertSpan( t, spans[1], "/book/{title}", attribute.String("net.host.name", "foobar"), attribute.Int("http.status_code", http.StatusOK), attribute.String("http.method", "GET"), attribute.String("http.route", "/book/{title}"), ) } func TestMultiFilters(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr)) retOK := func(req *restful.Request, resp *restful.Response) { resp.WriteHeader(http.StatusOK) } ws1 := &restful.WebService{} ws1.Path("/user") ws1.Route(ws1.GET("/{id}"). Filter(otelrestful.OTelFilter("my-service", otelrestful.WithTracerProvider(provider))). To(retOK)) ws1.Route(ws1.GET("/{id}/books"). Filter(otelrestful.OTelFilter("book-service", otelrestful.WithTracerProvider(provider))). To(retOK)) ws2 := &restful.WebService{} ws2.Path("/library") ws2.Filter(otelrestful.OTelFilter("library-service", otelrestful.WithTracerProvider(provider))) ws2.Route(ws2.GET("/{name}").To(retOK)) container := restful.NewContainer() container.Add(ws1) container.Add(ws2) r := httptest.NewRequest("GET", "/user/123", nil) w := httptest.NewRecorder() container.ServeHTTP(w, r) spans := sr.Ended() require.Len(t, spans, 1) assertSpan(t, spans[0], "/user/{id}") r = httptest.NewRequest("GET", "/user/123/books", nil) w = httptest.NewRecorder() container.ServeHTTP(w, r) spans = sr.Ended() require.Len(t, spans, 2) assertSpan(t, spans[1], "/user/{id}/books") r = httptest.NewRequest("GET", "/library/metropolitan", nil) w = httptest.NewRecorder() container.ServeHTTP(w, r) spans = sr.Ended() require.Len(t, spans, 3) assertSpan(t, spans[2], "/library/{name}") } func TestSpanStatus(t *testing.T) { testCases := []struct { httpStatusCode int wantSpanStatus codes.Code }{ {http.StatusOK, codes.Unset}, {http.StatusBadRequest, codes.Unset}, {http.StatusInternalServerError, codes.Error}, } for _, tc := range testCases { t.Run(strconv.Itoa(tc.httpStatusCode), func(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider() provider.RegisterSpanProcessor(sr) handlerFunc := func(req *restful.Request, resp *restful.Response) { resp.WriteHeader(tc.httpStatusCode) } ws := &restful.WebService{} ws.Route(ws.GET("/").To(handlerFunc)) container := restful.NewContainer() container.Filter(otelrestful.OTelFilter("my-service", otelrestful.WithTracerProvider(provider))) container.Add(ws) container.ServeHTTP(httptest.NewRecorder(), httptest.NewRequest("GET", "/", nil)) require.Len(t, sr.Ended(), 1, "should emit a span") assert.Equal(t, tc.wantSpanStatus, sr.Ended()[0].Status().Code, "should only set Error status for HTTP statuses >= 500") }) } } func TestWithPublicEndpoint(t *testing.T) { spanRecorder := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider( sdktrace.WithSpanProcessor(spanRecorder), ) remoteSpan := oteltrace.SpanContextConfig{ TraceID: oteltrace.TraceID{0x01}, SpanID: oteltrace.SpanID{0x01}, Remote: true, } prop := propagation.TraceContext{} handlerFunc := func(req *restful.Request, resp *restful.Response) { s := oteltrace.SpanFromContext(req.Request.Context()) sc := s.SpanContext() // Should be with new root trace. assert.True(t, sc.IsValid()) assert.False(t, sc.IsRemote()) assert.NotEqual(t, remoteSpan.TraceID, sc.TraceID()) } ws := &restful.WebService{} ws.Route(ws.GET("/user/{id}").To(handlerFunc)) container := restful.NewContainer() container.Filter(otelrestful.OTelFilter("test_handler", otelrestful.WithPublicEndpoint(), otelrestful.WithPropagators(prop), otelrestful.WithTracerProvider(provider)), ) container.Add(ws) r, err := http.NewRequest(http.MethodGet, "http://localhost/user/123", nil) require.NoError(t, err) sc := oteltrace.NewSpanContext(remoteSpan) ctx := oteltrace.ContextWithSpanContext(context.Background(), sc) prop.Inject(ctx, propagation.HeaderCarrier(r.Header)) rr := httptest.NewRecorder() container.ServeHTTP(rr, r) assert.Equal(t, 200, rr.Result().StatusCode) //nolint:bodyclose // False positive for httptest.ResponseRecorder: https://github.com/timakin/bodyclose/issues/59. // Recorded span should be linked with an incoming span context. assert.NoError(t, spanRecorder.ForceFlush(ctx)) done := spanRecorder.Ended() require.Len(t, done, 1) require.Len(t, done[0].Links(), 1, "should contain link") require.True(t, sc.Equal(done[0].Links()[0].SpanContext), "should link incoming span context") } func TestWithPublicEndpointFn(t *testing.T) { remoteSpan := oteltrace.SpanContextConfig{ TraceID: oteltrace.TraceID{0x01}, SpanID: oteltrace.SpanID{0x01}, TraceFlags: oteltrace.FlagsSampled, Remote: true, } prop := propagation.TraceContext{} for _, tt := range []struct { name string fn func(*http.Request) bool handlerAssert func(*testing.T, oteltrace.SpanContext) spansAssert func(*testing.T, oteltrace.SpanContext, []sdktrace.ReadOnlySpan) }{ { name: "with the method returning true", fn: func(r *http.Request) bool { return true }, handlerAssert: func(t *testing.T, sc oteltrace.SpanContext) { // Should be with new root trace. assert.True(t, sc.IsValid()) assert.False(t, sc.IsRemote()) assert.NotEqual(t, remoteSpan.TraceID, sc.TraceID()) }, spansAssert: func(t *testing.T, sc oteltrace.SpanContext, spans []sdktrace.ReadOnlySpan) { require.Len(t, spans, 1) require.Len(t, spans[0].Links(), 1, "should contain link") require.True(t, sc.Equal(spans[0].Links()[0].SpanContext), "should link incoming span context") }, }, { name: "with the method returning false", fn: func(r *http.Request) bool { return false }, handlerAssert: func(t *testing.T, sc oteltrace.SpanContext) { // Should have remote span as parent assert.True(t, sc.IsValid()) assert.False(t, sc.IsRemote()) assert.Equal(t, remoteSpan.TraceID, sc.TraceID()) }, spansAssert: func(t *testing.T, _ oteltrace.SpanContext, spans []sdktrace.ReadOnlySpan) { require.Len(t, spans, 1) require.Empty(t, spans[0].Links(), "should not contain link") }, }, } { t.Run(tt.name, func(t *testing.T) { spanRecorder := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider( sdktrace.WithSpanProcessor(spanRecorder), ) handlerFunc := func(req *restful.Request, resp *restful.Response) { s := oteltrace.SpanFromContext(req.Request.Context()) tt.handlerAssert(t, s.SpanContext()) } ws := &restful.WebService{} ws.Route(ws.GET("/user/{id}").To(handlerFunc)) container := restful.NewContainer() container.Filter(otelrestful.OTelFilter("test_handler", otelrestful.WithPublicEndpointFn(tt.fn), otelrestful.WithPropagators(prop), otelrestful.WithTracerProvider(provider)), ) container.Add(ws) r, err := http.NewRequest(http.MethodGet, "http://localhost/user/123", nil) require.NoError(t, err) sc := oteltrace.NewSpanContext(remoteSpan) ctx := oteltrace.ContextWithSpanContext(context.Background(), sc) prop.Inject(ctx, propagation.HeaderCarrier(r.Header)) rr := httptest.NewRecorder() container.ServeHTTP(rr, r) assert.Equal(t, http.StatusOK, rr.Result().StatusCode) //nolint:bodyclose // False positive for httptest.ResponseRecorder: https://github.com/timakin/bodyclose/issues/59. // Recorded span should be linked with an incoming span context. assert.NoError(t, spanRecorder.ForceFlush(ctx)) spans := spanRecorder.Ended() tt.spansAssert(t, sc, spans) }) } } func assertSpan(t *testing.T, span sdktrace.ReadOnlySpan, name string, attrs ...attribute.KeyValue) { assert.Equal(t, name, span.Name()) assert.Equal(t, oteltrace.SpanKindServer, span.SpanKind()) gotA := span.Attributes() for _, a := range attrs { assert.Contains(t, gotA, a) } } version.go000066400000000000000000000010701470323427300407060ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/emicklei/go-restful/otelrestful/test// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package test // import "go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful/test" // Version is the current release version of the go-restful instrumentation test module. func Version() string { return "0.56.0" // This string is updated by the pre_release.sh script during release } // SemVersion is the semantic version to be supplied to tracer/meter creation. // // Deprecated: Use [Version] instead. func SemVersion() string { return Version() } version.go000066400000000000000000000010561470323427300377330ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/emicklei/go-restful/otelrestful// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelrestful // import "go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful" // Version is the current release version of the go-restful instrumentation. func Version() string { return "0.56.0" // This string is updated by the pre_release.sh script during release } // SemVersion is the semantic version to be supplied to tracer/meter creation. // // Deprecated: Use [Version] instead. func SemVersion() string { return Version() } open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gin-gonic/000077500000000000000000000000001470323427300314255ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gin-gonic/gin/000077500000000000000000000000001470323427300322025ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gin-gonic/gin/otelgin/000077500000000000000000000000001470323427300336435ustar00rootroot00000000000000doc.go000066400000000000000000000007631470323427300346660ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gin-gonic/gin/otelgin// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package otelgin instruments the github.com/gin-gonic/gin package. // // Currently there are two ways the code can be instrumented. One is // instrumenting the routing of a received message (the Middleware function) // and instrumenting the response generation through template evaluation (the // HTML function). package otelgin // import "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin" example/000077500000000000000000000000001470323427300352175ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gin-gonic/gin/otelginDockerfile000066400000000000000000000004051470323427300372100ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gin-gonic/gin/otelgin/example# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 FROM golang:alpine AS base COPY . /src/ WORKDIR /src/instrumentation/github.com/gin-gonic/gin/otelgin/example FROM base AS gin-server RUN go install ./server.go CMD ["/go/bin/server"] README.md000066400000000000000000000012571470323427300365030ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gin-gonic/gin/otelgin/example# gin-gonic instrumentation example An HTTP server using gin-gonic and instrumentation. The server has a `/users/:id` endpoint. The server generates span information to `stdout`. These instructions expect you have [docker-compose](https://docs.docker.com/compose/) installed. Bring up the `gin-server` and `gin-client` services to run the example: ```sh docker-compose up --detach gin-server gin-client ``` The `gin-client` service sends just one HTTP request to `gin-server` and then exits. View the span generated by `gin-server` in the logs: ```sh docker-compose logs gin-server ``` Shut down the services when you are finished with the example: ```sh docker-compose down ``` docker-compose.yml000066400000000000000000000010501470323427300406500ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gin-gonic/gin/otelgin/example# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 version: "3.7" services: gin-client: image: golang:alpine networks: - example command: - "/bin/sh" - "-c" - "wget http://gin-server:8080/users/123 && cat 123" depends_on: - gin-server gin-server: build: dockerfile: $PWD/Dockerfile context: ../../../../../../ ports: - "8080:8080" command: - "/bin/sh" - "-c" - "/go/bin/server" networks: - example networks: example: go.mod000066400000000000000000000040311470323427300363230ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gin-gonic/gin/otelgin/examplemodule go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin/example go 1.22 replace ( go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin => ../ go.opentelemetry.io/contrib/propagators/b3 => ../../../../../../propagators/b3 ) require ( github.com/gin-gonic/gin v1.10.0 go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.56.0 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0 go.opentelemetry.io/otel/sdk v1.31.0 go.opentelemetry.io/otel/trace v1.31.0 ) require ( github.com/bytedance/sonic v1.12.3 // indirect github.com/bytedance/sonic/loader v0.2.0 // indirect github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/iasm v0.2.0 // indirect github.com/gabriel-vasile/mimetype v1.4.5 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.22.1 // indirect github.com/goccy/go-json v0.10.3 // indirect github.com/google/uuid v1.6.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.8 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect golang.org/x/arch v0.11.0 // indirect golang.org/x/crypto v0.28.0 // indirect golang.org/x/net v0.30.0 // indirect golang.org/x/sys v0.26.0 // indirect golang.org/x/text v0.19.0 // indirect google.golang.org/protobuf v1.35.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) go.sum000066400000000000000000000214351470323427300363570ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gin-gonic/gin/otelgin/examplegithub.com/bytedance/sonic v1.12.3 h1:W2MGa7RCU1QTeYRTPE3+88mVC0yXmsRQRChiyVocVjU= github.com/bytedance/sonic v1.12.3/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/bytedance/sonic/loader v0.2.0 h1:zNprn+lsIP06C/IqCHs3gPQIvnvpKbbxyXQP1iU4kWM= github.com/bytedance/sonic/loader v0.2.0/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4= github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA= github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0 h1:UGZ1QwZWY67Z6BmckTU+9Rxn04m2bD3gD6Mk0OIOCPk= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0/go.mod h1:fcwWuDuaObkkChiDlhEpSq9+X1C0omv+s5mBtToAQ64= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= golang.org/x/arch v0.11.0 h1:KXV8WWKCXm6tRpLirl2szsO5j/oOODwZf4hATmGVNs4= golang.org/x/arch v0.11.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= server.go000066400000000000000000000037711470323427300370640ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gin-gonic/gin/otelgin/example// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package main import ( "context" "html/template" "log" "net/http" "github.com/gin-gonic/gin" "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/propagation" sdktrace "go.opentelemetry.io/otel/sdk/trace" oteltrace "go.opentelemetry.io/otel/trace" ) var tracer = otel.Tracer("gin-server") func main() { tp, err := initTracer() if err != nil { log.Fatal(err) } defer func() { if err := tp.Shutdown(context.Background()); err != nil { log.Printf("Error shutting down tracer provider: %v", err) } }() r := gin.New() r.Use(otelgin.Middleware("my-server")) tmplName := "user" tmplStr := "user {{ .name }} (id {{ .id }})\n" tmpl := template.Must(template.New(tmplName).Parse(tmplStr)) r.SetHTMLTemplate(tmpl) r.GET("/users/:id", func(c *gin.Context) { id := c.Param("id") name := getUser(c, id) otelgin.HTML(c, http.StatusOK, tmplName, gin.H{ "name": name, "id": id, }) }) _ = r.Run(":8080") } func initTracer() (*sdktrace.TracerProvider, error) { exporter, err := stdout.New(stdout.WithPrettyPrint()) if err != nil { return nil, err } tp := sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithBatcher(exporter), ) otel.SetTracerProvider(tp) otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) return tp, nil } func getUser(c *gin.Context, id string) string { // Pass the built-in `context.Context` object from http.Request to OpenTelemetry APIs // where required. It is available from gin.Context.Request.Context() _, span := tracer.Start(c.Request.Context(), "getUser", oteltrace.WithAttributes(attribute.String("id", id))) defer span.End() if id == "123" { return "otelgin tester" } return "unknown" } gintrace.go000066400000000000000000000077651470323427300357260ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gin-gonic/gin/otelgin// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Based on https://github.com/DataDog/dd-trace-go/blob/8fb554ff7cf694267f9077ae35e27ce4689ed8b6/contrib/gin-gonic/gin/gintrace.go package otelgin // import "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin" import ( "fmt" "github.com/gin-gonic/gin" "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconvutil" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/propagation" semconv "go.opentelemetry.io/otel/semconv/v1.20.0" oteltrace "go.opentelemetry.io/otel/trace" ) const ( tracerKey = "otel-go-contrib-tracer" // ScopeName is the instrumentation scope name. ScopeName = "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin" ) // Middleware returns middleware that will trace incoming requests. // The service parameter should describe the name of the (virtual) // server handling the request. func Middleware(service string, opts ...Option) gin.HandlerFunc { cfg := config{} for _, opt := range opts { opt.apply(&cfg) } if cfg.TracerProvider == nil { cfg.TracerProvider = otel.GetTracerProvider() } tracer := cfg.TracerProvider.Tracer( ScopeName, oteltrace.WithInstrumentationVersion(Version()), ) if cfg.Propagators == nil { cfg.Propagators = otel.GetTextMapPropagator() } return func(c *gin.Context) { for _, f := range cfg.Filters { if !f(c.Request) { // Serve the request to the next middleware // if a filter rejects the request. c.Next() return } } for _, f := range cfg.GinFilters { if !f(c) { // Serve the request to the next middleware // if a filter rejects the request. c.Next() return } } c.Set(tracerKey, tracer) savedCtx := c.Request.Context() defer func() { c.Request = c.Request.WithContext(savedCtx) }() ctx := cfg.Propagators.Extract(savedCtx, propagation.HeaderCarrier(c.Request.Header)) opts := []oteltrace.SpanStartOption{ oteltrace.WithAttributes(semconvutil.HTTPServerRequest(service, c.Request)...), oteltrace.WithAttributes(semconv.HTTPRoute(c.FullPath())), oteltrace.WithSpanKind(oteltrace.SpanKindServer), } var spanName string if cfg.SpanNameFormatter == nil { spanName = c.FullPath() } else { spanName = cfg.SpanNameFormatter(c.Request) } if spanName == "" { spanName = fmt.Sprintf("HTTP %s route not found", c.Request.Method) } ctx, span := tracer.Start(ctx, spanName, opts...) defer span.End() // pass the span through the request context c.Request = c.Request.WithContext(ctx) // serve the request to the next middleware c.Next() status := c.Writer.Status() span.SetStatus(semconvutil.HTTPServerStatus(status)) if status > 0 { span.SetAttributes(semconv.HTTPStatusCode(status)) } if len(c.Errors) > 0 { span.SetAttributes(attribute.String("gin.errors", c.Errors.String())) } } } // HTML will trace the rendering of the template as a child of the // span in the given context. This is a replacement for // gin.Context.HTML function - it invokes the original function after // setting up the span. func HTML(c *gin.Context, code int, name string, obj interface{}) { var tracer oteltrace.Tracer tracerInterface, ok := c.Get(tracerKey) if ok { tracer, ok = tracerInterface.(oteltrace.Tracer) } if !ok { tracer = otel.GetTracerProvider().Tracer( ScopeName, oteltrace.WithInstrumentationVersion(Version()), ) } savedContext := c.Request.Context() defer func() { c.Request = c.Request.WithContext(savedContext) }() opt := oteltrace.WithAttributes(attribute.String("go.template", name)) _, span := tracer.Start(savedContext, "gin.renderer.html", opt) defer func() { if r := recover(); r != nil { err := fmt.Errorf("error rendering template:%s: %s", name, r) span.RecordError(err) span.SetStatus(codes.Error, "template failure") span.End() panic(r) } span.End() }() c.HTML(code, name, obj) } gintrace_test.go000066400000000000000000000057051470323427300367550ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gin-gonic/gin/otelgin// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Based on https://github.com/DataDog/dd-trace-go/blob/8fb554ff7cf694267f9077ae35e27ce4689ed8b6/contrib/gin-gonic/gin/gintrace_test.go package otelgin import ( "context" "net/http" "net/http/httptest" "testing" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace/noop" b3prop "go.opentelemetry.io/contrib/propagators/b3" ) func init() { gin.SetMode(gin.ReleaseMode) // silence annoying log msgs } func TestGetSpanNotInstrumented(t *testing.T) { router := gin.New() router.GET("/ping", func(c *gin.Context) { // Assert we don't have a span on the context. span := trace.SpanFromContext(c.Request.Context()) ok := !span.SpanContext().IsValid() assert.True(t, ok) _, _ = c.Writer.Write([]byte("ok")) }) r := httptest.NewRequest("GET", "/ping", nil) w := httptest.NewRecorder() router.ServeHTTP(w, r) response := w.Result() //nolint:bodyclose // False positive for httptest.ResponseRecorder: https://github.com/timakin/bodyclose/issues/59. assert.Equal(t, http.StatusOK, response.StatusCode) } func TestPropagationWithGlobalPropagators(t *testing.T) { provider := noop.NewTracerProvider() otel.SetTextMapPropagator(b3prop.New()) r := httptest.NewRequest("GET", "/user/123", nil) w := httptest.NewRecorder() ctx := context.Background() sc := trace.NewSpanContext(trace.SpanContextConfig{ TraceID: trace.TraceID{0x01}, SpanID: trace.SpanID{0x01}, }) ctx = trace.ContextWithRemoteSpanContext(ctx, sc) ctx, _ = provider.Tracer(ScopeName).Start(ctx, "test") otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(r.Header)) router := gin.New() router.Use(Middleware("foobar", WithTracerProvider(provider))) router.GET("/user/:id", func(c *gin.Context) { span := trace.SpanFromContext(c.Request.Context()) assert.Equal(t, sc.TraceID(), span.SpanContext().TraceID()) assert.Equal(t, sc.SpanID(), span.SpanContext().SpanID()) }) router.ServeHTTP(w, r) } func TestPropagationWithCustomPropagators(t *testing.T) { provider := noop.NewTracerProvider() b3 := b3prop.New() r := httptest.NewRequest("GET", "/user/123", nil) w := httptest.NewRecorder() ctx := context.Background() sc := trace.NewSpanContext(trace.SpanContextConfig{ TraceID: trace.TraceID{0x01}, SpanID: trace.SpanID{0x01}, }) ctx = trace.ContextWithRemoteSpanContext(ctx, sc) ctx, _ = provider.Tracer(ScopeName).Start(ctx, "test") b3.Inject(ctx, propagation.HeaderCarrier(r.Header)) router := gin.New() router.Use(Middleware("foobar", WithTracerProvider(provider), WithPropagators(b3))) router.GET("/user/:id", func(c *gin.Context) { span := trace.SpanFromContext(c.Request.Context()) assert.Equal(t, sc.TraceID(), span.SpanContext().TraceID()) assert.Equal(t, sc.SpanID(), span.SpanContext().SpanID()) }) router.ServeHTTP(w, r) } go.mod000066400000000000000000000036071470323427300347000ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gin-gonic/gin/otelginmodule go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin go 1.22 replace go.opentelemetry.io/contrib/propagators/b3 => ../../../../../propagators/b3 require ( github.com/gin-gonic/gin v1.10.0 github.com/stretchr/testify v1.9.0 go.opentelemetry.io/contrib/propagators/b3 v1.31.0 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/trace v1.31.0 ) require ( github.com/bytedance/sonic v1.12.3 // indirect github.com/bytedance/sonic/loader v0.2.0 // indirect github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/iasm v0.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/gabriel-vasile/mimetype v1.4.5 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.22.1 // indirect github.com/goccy/go-json v0.10.3 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.8 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect golang.org/x/arch v0.11.0 // indirect golang.org/x/crypto v0.28.0 // indirect golang.org/x/net v0.30.0 // indirect golang.org/x/sys v0.26.0 // indirect golang.org/x/text v0.19.0 // indirect google.golang.org/protobuf v1.35.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) go.sum000066400000000000000000000203461470323427300347240ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gin-gonic/gin/otelgingithub.com/bytedance/sonic v1.12.3 h1:W2MGa7RCU1QTeYRTPE3+88mVC0yXmsRQRChiyVocVjU= github.com/bytedance/sonic v1.12.3/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/bytedance/sonic/loader v0.2.0 h1:zNprn+lsIP06C/IqCHs3gPQIvnvpKbbxyXQP1iU4kWM= github.com/bytedance/sonic/loader v0.2.0/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4= github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA= github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= golang.org/x/arch v0.11.0 h1:KXV8WWKCXm6tRpLirl2szsO5j/oOODwZf4hATmGVNs4= golang.org/x/arch v0.11.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= internal/000077500000000000000000000000001470323427300354005ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gin-gonic/gin/otelginsemconvutil/000077500000000000000000000000001470323427300377505ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gin-gonic/gin/otelgin/internalgen.go000066400000000000000000000013631470323427300410530ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconvutil// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconvutil" // Generate semconvutil package: //go:generate gotmpl --body=../../../../../../../internal/shared/semconvutil/httpconv_test.go.tmpl "--data={}" --out=httpconv_test.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconvutil/httpconv.go.tmpl "--data={}" --out=httpconv.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconvutil/netconv_test.go.tmpl "--data={}" --out=netconv_test.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconvutil/netconv.go.tmpl "--data={}" --out=netconv.go httpconv.go000066400000000000000000000466201470323427300421540ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconvutil// Code created by gotmpl. DO NOT MODIFY. // source: internal/shared/semconvutil/httpconv.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconvutil" import ( "fmt" "net/http" "strings" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" semconv "go.opentelemetry.io/otel/semconv/v1.20.0" ) // HTTPClientResponse returns trace attributes for an HTTP response received by a // client from a server. It will return the following attributes if the related // values are defined in resp: "http.status.code", // "http.response_content_length". // // This does not add all OpenTelemetry required attributes for an HTTP event, // it assumes ClientRequest was used to create the span with a complete set of // attributes. If a complete set of attributes can be generated using the // request contained in resp. For example: // // append(HTTPClientResponse(resp), ClientRequest(resp.Request)...) func HTTPClientResponse(resp *http.Response) []attribute.KeyValue { return hc.ClientResponse(resp) } // HTTPClientRequest returns trace attributes for an HTTP request made by a client. // The following attributes are always returned: "http.url", "http.method", // "net.peer.name". The following attributes are returned if the related values // are defined in req: "net.peer.port", "user_agent.original", // "http.request_content_length". func HTTPClientRequest(req *http.Request) []attribute.KeyValue { return hc.ClientRequest(req) } // HTTPClientRequestMetrics returns metric attributes for an HTTP request made by a client. // The following attributes are always returned: "http.method", "net.peer.name". // The following attributes are returned if the // related values are defined in req: "net.peer.port". func HTTPClientRequestMetrics(req *http.Request) []attribute.KeyValue { return hc.ClientRequestMetrics(req) } // HTTPClientStatus returns a span status code and message for an HTTP status code // value received by a client. func HTTPClientStatus(code int) (codes.Code, string) { return hc.ClientStatus(code) } // HTTPServerRequest returns trace attributes for an HTTP request received by a // server. // // The server must be the primary server name if it is known. For example this // would be the ServerName directive // (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache // server, and the server_name directive // (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an // nginx server. More generically, the primary server name would be the host // header value that matches the default virtual host of an HTTP server. It // should include the host identifier and if a port is used to route to the // server that port identifier should be included as an appropriate port // suffix. // // If the primary server name is not known, server should be an empty string. // The req Host will be used to determine the server instead. // // The following attributes are always returned: "http.method", "http.scheme", // "http.target", "net.host.name". The following attributes are returned if // they related values are defined in req: "net.host.port", "net.sock.peer.addr", // "net.sock.peer.port", "user_agent.original", "http.client_ip". func HTTPServerRequest(server string, req *http.Request) []attribute.KeyValue { return hc.ServerRequest(server, req) } // HTTPServerRequestMetrics returns metric attributes for an HTTP request received by a // server. // // The server must be the primary server name if it is known. For example this // would be the ServerName directive // (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache // server, and the server_name directive // (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an // nginx server. More generically, the primary server name would be the host // header value that matches the default virtual host of an HTTP server. It // should include the host identifier and if a port is used to route to the // server that port identifier should be included as an appropriate port // suffix. // // If the primary server name is not known, server should be an empty string. // The req Host will be used to determine the server instead. // // The following attributes are always returned: "http.method", "http.scheme", // "net.host.name". The following attributes are returned if they related // values are defined in req: "net.host.port". func HTTPServerRequestMetrics(server string, req *http.Request) []attribute.KeyValue { return hc.ServerRequestMetrics(server, req) } // HTTPServerStatus returns a span status code and message for an HTTP status code // value returned by a server. Status codes in the 400-499 range are not // returned as errors. func HTTPServerStatus(code int) (codes.Code, string) { return hc.ServerStatus(code) } // httpConv are the HTTP semantic convention attributes defined for a version // of the OpenTelemetry specification. type httpConv struct { NetConv *netConv HTTPClientIPKey attribute.Key HTTPMethodKey attribute.Key HTTPRequestContentLengthKey attribute.Key HTTPResponseContentLengthKey attribute.Key HTTPRouteKey attribute.Key HTTPSchemeHTTP attribute.KeyValue HTTPSchemeHTTPS attribute.KeyValue HTTPStatusCodeKey attribute.Key HTTPTargetKey attribute.Key HTTPURLKey attribute.Key UserAgentOriginalKey attribute.Key } var hc = &httpConv{ NetConv: nc, HTTPClientIPKey: semconv.HTTPClientIPKey, HTTPMethodKey: semconv.HTTPMethodKey, HTTPRequestContentLengthKey: semconv.HTTPRequestContentLengthKey, HTTPResponseContentLengthKey: semconv.HTTPResponseContentLengthKey, HTTPRouteKey: semconv.HTTPRouteKey, HTTPSchemeHTTP: semconv.HTTPSchemeHTTP, HTTPSchemeHTTPS: semconv.HTTPSchemeHTTPS, HTTPStatusCodeKey: semconv.HTTPStatusCodeKey, HTTPTargetKey: semconv.HTTPTargetKey, HTTPURLKey: semconv.HTTPURLKey, UserAgentOriginalKey: semconv.UserAgentOriginalKey, } // ClientResponse returns attributes for an HTTP response received by a client // from a server. The following attributes are returned if the related values // are defined in resp: "http.status.code", "http.response_content_length". // // This does not add all OpenTelemetry required attributes for an HTTP event, // it assumes ClientRequest was used to create the span with a complete set of // attributes. If a complete set of attributes can be generated using the // request contained in resp. For example: // // append(ClientResponse(resp), ClientRequest(resp.Request)...) func (c *httpConv) ClientResponse(resp *http.Response) []attribute.KeyValue { /* The following semantic conventions are returned if present: http.status_code int http.response_content_length int */ var n int if resp.StatusCode > 0 { n++ } if resp.ContentLength > 0 { n++ } attrs := make([]attribute.KeyValue, 0, n) if resp.StatusCode > 0 { attrs = append(attrs, c.HTTPStatusCodeKey.Int(resp.StatusCode)) } if resp.ContentLength > 0 { attrs = append(attrs, c.HTTPResponseContentLengthKey.Int(int(resp.ContentLength))) } return attrs } // ClientRequest returns attributes for an HTTP request made by a client. The // following attributes are always returned: "http.url", "http.method", // "net.peer.name". The following attributes are returned if the related values // are defined in req: "net.peer.port", "user_agent.original", // "http.request_content_length", "user_agent.original". func (c *httpConv) ClientRequest(req *http.Request) []attribute.KeyValue { /* The following semantic conventions are returned if present: http.method string user_agent.original string http.url string net.peer.name string net.peer.port int http.request_content_length int */ /* The following semantic conventions are not returned: http.status_code This requires the response. See ClientResponse. http.response_content_length This requires the response. See ClientResponse. net.sock.family This requires the socket used. net.sock.peer.addr This requires the socket used. net.sock.peer.name This requires the socket used. net.sock.peer.port This requires the socket used. http.resend_count This is something outside of a single request. net.protocol.name The value is the Request is ignored, and the go client will always use "http". net.protocol.version The value in the Request is ignored, and the go client will always use 1.1 or 2.0. */ n := 3 // URL, peer name, proto, and method. var h string if req.URL != nil { h = req.URL.Host } peer, p := firstHostPort(h, req.Header.Get("Host")) port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", p) if port > 0 { n++ } useragent := req.UserAgent() if useragent != "" { n++ } if req.ContentLength > 0 { n++ } attrs := make([]attribute.KeyValue, 0, n) attrs = append(attrs, c.method(req.Method)) var u string if req.URL != nil { // Remove any username/password info that may be in the URL. userinfo := req.URL.User req.URL.User = nil u = req.URL.String() // Restore any username/password info that was removed. req.URL.User = userinfo } attrs = append(attrs, c.HTTPURLKey.String(u)) attrs = append(attrs, c.NetConv.PeerName(peer)) if port > 0 { attrs = append(attrs, c.NetConv.PeerPort(port)) } if useragent != "" { attrs = append(attrs, c.UserAgentOriginalKey.String(useragent)) } if l := req.ContentLength; l > 0 { attrs = append(attrs, c.HTTPRequestContentLengthKey.Int64(l)) } return attrs } // ClientRequestMetrics returns metric attributes for an HTTP request made by a client. The // following attributes are always returned: "http.method", "net.peer.name". // The following attributes are returned if the related values // are defined in req: "net.peer.port". func (c *httpConv) ClientRequestMetrics(req *http.Request) []attribute.KeyValue { /* The following semantic conventions are returned if present: http.method string net.peer.name string net.peer.port int */ n := 2 // method, peer name. var h string if req.URL != nil { h = req.URL.Host } peer, p := firstHostPort(h, req.Header.Get("Host")) port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", p) if port > 0 { n++ } attrs := make([]attribute.KeyValue, 0, n) attrs = append(attrs, c.method(req.Method), c.NetConv.PeerName(peer)) if port > 0 { attrs = append(attrs, c.NetConv.PeerPort(port)) } return attrs } // ServerRequest returns attributes for an HTTP request received by a server. // // The server must be the primary server name if it is known. For example this // would be the ServerName directive // (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache // server, and the server_name directive // (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an // nginx server. More generically, the primary server name would be the host // header value that matches the default virtual host of an HTTP server. It // should include the host identifier and if a port is used to route to the // server that port identifier should be included as an appropriate port // suffix. // // If the primary server name is not known, server should be an empty string. // The req Host will be used to determine the server instead. // // The following attributes are always returned: "http.method", "http.scheme", // "http.target", "net.host.name". The following attributes are returned if they // related values are defined in req: "net.host.port", "net.sock.peer.addr", // "net.sock.peer.port", "user_agent.original", "http.client_ip", // "net.protocol.name", "net.protocol.version". func (c *httpConv) ServerRequest(server string, req *http.Request) []attribute.KeyValue { /* The following semantic conventions are returned if present: http.method string http.scheme string net.host.name string net.host.port int net.sock.peer.addr string net.sock.peer.port int user_agent.original string http.client_ip string net.protocol.name string Note: not set if the value is "http". net.protocol.version string http.target string Note: doesn't include the query parameter. */ /* The following semantic conventions are not returned: http.status_code This requires the response. http.request_content_length This requires the len() of body, which can mutate it. http.response_content_length This requires the response. http.route This is not available. net.sock.peer.name This would require a DNS lookup. net.sock.host.addr The request doesn't have access to the underlying socket. net.sock.host.port The request doesn't have access to the underlying socket. */ n := 4 // Method, scheme, proto, and host name. var host string var p int if server == "" { host, p = splitHostPort(req.Host) } else { // Prioritize the primary server name. host, p = splitHostPort(server) if p < 0 { _, p = splitHostPort(req.Host) } } hostPort := requiredHTTPPort(req.TLS != nil, p) if hostPort > 0 { n++ } peer, peerPort := splitHostPort(req.RemoteAddr) if peer != "" { n++ if peerPort > 0 { n++ } } useragent := req.UserAgent() if useragent != "" { n++ } clientIP := serverClientIP(req.Header.Get("X-Forwarded-For")) if clientIP != "" { n++ } var target string if req.URL != nil { target = req.URL.Path if target != "" { n++ } } protoName, protoVersion := netProtocol(req.Proto) if protoName != "" && protoName != "http" { n++ } if protoVersion != "" { n++ } attrs := make([]attribute.KeyValue, 0, n) attrs = append(attrs, c.method(req.Method)) attrs = append(attrs, c.scheme(req.TLS != nil)) attrs = append(attrs, c.NetConv.HostName(host)) if hostPort > 0 { attrs = append(attrs, c.NetConv.HostPort(hostPort)) } if peer != "" { // The Go HTTP server sets RemoteAddr to "IP:port", this will not be a // file-path that would be interpreted with a sock family. attrs = append(attrs, c.NetConv.SockPeerAddr(peer)) if peerPort > 0 { attrs = append(attrs, c.NetConv.SockPeerPort(peerPort)) } } if useragent != "" { attrs = append(attrs, c.UserAgentOriginalKey.String(useragent)) } if clientIP != "" { attrs = append(attrs, c.HTTPClientIPKey.String(clientIP)) } if target != "" { attrs = append(attrs, c.HTTPTargetKey.String(target)) } if protoName != "" && protoName != "http" { attrs = append(attrs, c.NetConv.NetProtocolName.String(protoName)) } if protoVersion != "" { attrs = append(attrs, c.NetConv.NetProtocolVersion.String(protoVersion)) } return attrs } // ServerRequestMetrics returns metric attributes for an HTTP request received // by a server. // // The server must be the primary server name if it is known. For example this // would be the ServerName directive // (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache // server, and the server_name directive // (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an // nginx server. More generically, the primary server name would be the host // header value that matches the default virtual host of an HTTP server. It // should include the host identifier and if a port is used to route to the // server that port identifier should be included as an appropriate port // suffix. // // If the primary server name is not known, server should be an empty string. // The req Host will be used to determine the server instead. // // The following attributes are always returned: "http.method", "http.scheme", // "net.host.name". The following attributes are returned if they related // values are defined in req: "net.host.port". func (c *httpConv) ServerRequestMetrics(server string, req *http.Request) []attribute.KeyValue { /* The following semantic conventions are returned if present: http.scheme string http.route string http.method string http.status_code int net.host.name string net.host.port int net.protocol.name string Note: not set if the value is "http". net.protocol.version string */ n := 3 // Method, scheme, and host name. var host string var p int if server == "" { host, p = splitHostPort(req.Host) } else { // Prioritize the primary server name. host, p = splitHostPort(server) if p < 0 { _, p = splitHostPort(req.Host) } } hostPort := requiredHTTPPort(req.TLS != nil, p) if hostPort > 0 { n++ } protoName, protoVersion := netProtocol(req.Proto) if protoName != "" { n++ } if protoVersion != "" { n++ } attrs := make([]attribute.KeyValue, 0, n) attrs = append(attrs, c.methodMetric(req.Method)) attrs = append(attrs, c.scheme(req.TLS != nil)) attrs = append(attrs, c.NetConv.HostName(host)) if hostPort > 0 { attrs = append(attrs, c.NetConv.HostPort(hostPort)) } if protoName != "" { attrs = append(attrs, c.NetConv.NetProtocolName.String(protoName)) } if protoVersion != "" { attrs = append(attrs, c.NetConv.NetProtocolVersion.String(protoVersion)) } return attrs } func (c *httpConv) method(method string) attribute.KeyValue { if method == "" { return c.HTTPMethodKey.String(http.MethodGet) } return c.HTTPMethodKey.String(method) } func (c *httpConv) methodMetric(method string) attribute.KeyValue { method = strings.ToUpper(method) switch method { case http.MethodConnect, http.MethodDelete, http.MethodGet, http.MethodHead, http.MethodOptions, http.MethodPatch, http.MethodPost, http.MethodPut, http.MethodTrace: default: method = "_OTHER" } return c.HTTPMethodKey.String(method) } func (c *httpConv) scheme(https bool) attribute.KeyValue { // nolint:revive if https { return c.HTTPSchemeHTTPS } return c.HTTPSchemeHTTP } func serverClientIP(xForwardedFor string) string { if idx := strings.Index(xForwardedFor, ","); idx >= 0 { xForwardedFor = xForwardedFor[:idx] } return xForwardedFor } func requiredHTTPPort(https bool, port int) int { // nolint:revive if https { if port > 0 && port != 443 { return port } } else { if port > 0 && port != 80 { return port } } return -1 } // Return the request host and port from the first non-empty source. func firstHostPort(source ...string) (host string, port int) { for _, hostport := range source { host, port = splitHostPort(hostport) if host != "" || port > 0 { break } } return } // ClientStatus returns a span status code and message for an HTTP status code // value received by a client. func (c *httpConv) ClientStatus(code int) (codes.Code, string) { if code < 100 || code >= 600 { return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) } if code >= 400 { return codes.Error, "" } return codes.Unset, "" } // ServerStatus returns a span status code and message for an HTTP status code // value returned by a server. Status codes in the 400-499 range are not // returned as errors. func (c *httpConv) ServerStatus(code int) (codes.Code, string) { if code < 100 || code >= 600 { return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) } if code >= 500 { return codes.Error, "" } return codes.Unset, "" } httpconv_test.go000066400000000000000000000372171470323427300432150ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconvutil// Code created by gotmpl. DO NOT MODIFY. // source: internal/shared/semconvutil/httpconv_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconvutil import ( "net/http" "net/http/httptest" "net/url" "strconv" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" ) func TestHTTPClientResponse(t *testing.T) { const stat, n = 201, 397 resp := &http.Response{ StatusCode: stat, ContentLength: n, } got := HTTPClientResponse(resp) assert.Equal(t, 2, cap(got), "slice capacity") assert.ElementsMatch(t, []attribute.KeyValue{ attribute.Key("http.status_code").Int(stat), attribute.Key("http.response_content_length").Int(n), }, got) } func TestHTTPSClientRequest(t *testing.T) { req := &http.Request{ Method: http.MethodGet, URL: &url.URL{ Scheme: "https", Host: "127.0.0.1:443", Path: "/resource", }, Proto: "HTTP/1.0", ProtoMajor: 1, ProtoMinor: 0, } assert.ElementsMatch( t, []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.url", "https://127.0.0.1:443/resource"), attribute.String("net.peer.name", "127.0.0.1"), }, HTTPClientRequest(req), ) } func TestHTTPSClientRequestMetrics(t *testing.T) { req := &http.Request{ Method: http.MethodGet, URL: &url.URL{ Scheme: "https", Host: "127.0.0.1:443", Path: "/resource", }, Proto: "HTTP/1.0", ProtoMajor: 1, ProtoMinor: 0, } assert.ElementsMatch( t, []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("net.peer.name", "127.0.0.1"), }, HTTPClientRequestMetrics(req), ) } func TestHTTPClientRequest(t *testing.T) { const ( user = "alice" n = 128 agent = "Go-http-client/1.1" ) req := &http.Request{ Method: http.MethodGet, URL: &url.URL{ Scheme: "http", Host: "127.0.0.1:8080", Path: "/resource", }, Proto: "HTTP/1.0", ProtoMajor: 1, ProtoMinor: 0, Header: http.Header{ "User-Agent": []string{agent}, }, ContentLength: n, } req.SetBasicAuth(user, "pswrd") assert.ElementsMatch( t, []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.url", "http://127.0.0.1:8080/resource"), attribute.String("net.peer.name", "127.0.0.1"), attribute.Int("net.peer.port", 8080), attribute.String("user_agent.original", agent), attribute.Int("http.request_content_length", n), }, HTTPClientRequest(req), ) } func TestHTTPClientRequestMetrics(t *testing.T) { const ( user = "alice" n = 128 agent = "Go-http-client/1.1" ) req := &http.Request{ Method: http.MethodGet, URL: &url.URL{ Scheme: "http", Host: "127.0.0.1:8080", Path: "/resource", }, Proto: "HTTP/1.0", ProtoMajor: 1, ProtoMinor: 0, Header: http.Header{ "User-Agent": []string{agent}, }, ContentLength: n, } req.SetBasicAuth(user, "pswrd") assert.ElementsMatch( t, []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("net.peer.name", "127.0.0.1"), attribute.Int("net.peer.port", 8080), }, HTTPClientRequestMetrics(req), ) } func TestHTTPClientRequestRequired(t *testing.T) { req := new(http.Request) var got []attribute.KeyValue assert.NotPanics(t, func() { got = HTTPClientRequest(req) }) want := []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.url", ""), attribute.String("net.peer.name", ""), } assert.Equal(t, want, got) } func TestHTTPServerRequest(t *testing.T) { got := make(chan *http.Request, 1) handler := func(w http.ResponseWriter, r *http.Request) { got <- r w.WriteHeader(http.StatusOK) } srv := httptest.NewServer(http.HandlerFunc(handler)) defer srv.Close() srvURL, err := url.Parse(srv.URL) require.NoError(t, err) srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) require.NoError(t, err) resp, err := srv.Client().Get(srv.URL) require.NoError(t, err) require.NoError(t, resp.Body.Close()) req := <-got peer, peerPort := splitHostPort(req.RemoteAddr) const user = "alice" req.SetBasicAuth(user, "pswrd") const clientIP = "127.0.0.5" req.Header.Add("X-Forwarded-For", clientIP) assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.scheme", "http"), attribute.String("net.host.name", srvURL.Hostname()), attribute.Int("net.host.port", int(srvPort)), attribute.String("net.sock.peer.addr", peer), attribute.Int("net.sock.peer.port", peerPort), attribute.String("user_agent.original", "Go-http-client/1.1"), attribute.String("http.client_ip", clientIP), attribute.String("net.protocol.version", "1.1"), attribute.String("http.target", "/"), }, HTTPServerRequest("", req)) } func TestHTTPServerRequestMetrics(t *testing.T) { got := make(chan *http.Request, 1) handler := func(w http.ResponseWriter, r *http.Request) { got <- r w.WriteHeader(http.StatusOK) } srv := httptest.NewServer(http.HandlerFunc(handler)) defer srv.Close() srvURL, err := url.Parse(srv.URL) require.NoError(t, err) srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) require.NoError(t, err) resp, err := srv.Client().Get(srv.URL) require.NoError(t, err) require.NoError(t, resp.Body.Close()) req := <-got assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.scheme", "http"), attribute.String("net.host.name", srvURL.Hostname()), attribute.Int("net.host.port", int(srvPort)), attribute.String("net.protocol.name", "http"), attribute.String("net.protocol.version", "1.1"), }, HTTPServerRequestMetrics("", req)) } func TestHTTPServerName(t *testing.T) { req := new(http.Request) var got []attribute.KeyValue const ( host = "test.semconv.server" port = 8080 ) portStr := strconv.Itoa(port) server := host + ":" + portStr assert.NotPanics(t, func() { got = HTTPServerRequest(server, req) }) assert.Contains(t, got, attribute.String("net.host.name", host)) assert.Contains(t, got, attribute.Int("net.host.port", port)) req = &http.Request{Host: "alt.host.name:" + portStr} // The server parameter does not include a port, ServerRequest should use // the port in the request Host field. assert.NotPanics(t, func() { got = HTTPServerRequest(host, req) }) assert.Contains(t, got, attribute.String("net.host.name", host)) assert.Contains(t, got, attribute.Int("net.host.port", port)) } func TestHTTPServerRequestFailsGracefully(t *testing.T) { req := new(http.Request) var got []attribute.KeyValue assert.NotPanics(t, func() { got = HTTPServerRequest("", req) }) want := []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.scheme", "http"), attribute.String("net.host.name", ""), } assert.ElementsMatch(t, want, got) } func TestHTTPMethod(t *testing.T) { assert.Equal(t, attribute.String("http.method", "POST"), hc.method("POST")) assert.Equal(t, attribute.String("http.method", "GET"), hc.method("")) assert.Equal(t, attribute.String("http.method", "garbage"), hc.method("garbage")) } func TestHTTPScheme(t *testing.T) { assert.Equal(t, attribute.String("http.scheme", "http"), hc.scheme(false)) assert.Equal(t, attribute.String("http.scheme", "https"), hc.scheme(true)) } func TestHTTPServerClientIP(t *testing.T) { tests := []struct { xForwardedFor string want string }{ {"", ""}, {"127.0.0.1", "127.0.0.1"}, {"127.0.0.1,127.0.0.5", "127.0.0.1"}, } for _, test := range tests { got := serverClientIP(test.xForwardedFor) assert.Equal(t, test.want, got, test.xForwardedFor) } } func TestRequiredHTTPPort(t *testing.T) { tests := []struct { https bool port int want int }{ {true, 443, -1}, {true, 80, 80}, {true, 8081, 8081}, {false, 443, 443}, {false, 80, -1}, {false, 8080, 8080}, } for _, test := range tests { got := requiredHTTPPort(test.https, test.port) assert.Equal(t, test.want, got, test.https, test.port) } } func TestFirstHostPort(t *testing.T) { host, port := "127.0.0.1", 8080 hostport := "127.0.0.1:8080" sources := [][]string{ {hostport}, {"", hostport}, {"", "", hostport}, {"", "", hostport, ""}, {"", "", hostport, "127.0.0.3:80"}, } for _, src := range sources { h, p := firstHostPort(src...) assert.Equal(t, host, h, src) assert.Equal(t, port, p, src) } } func TestHTTPClientStatus(t *testing.T) { tests := []struct { code int stat codes.Code msg bool }{ {0, codes.Error, true}, {http.StatusContinue, codes.Unset, false}, {http.StatusSwitchingProtocols, codes.Unset, false}, {http.StatusProcessing, codes.Unset, false}, {http.StatusEarlyHints, codes.Unset, false}, {http.StatusOK, codes.Unset, false}, {http.StatusCreated, codes.Unset, false}, {http.StatusAccepted, codes.Unset, false}, {http.StatusNonAuthoritativeInfo, codes.Unset, false}, {http.StatusNoContent, codes.Unset, false}, {http.StatusResetContent, codes.Unset, false}, {http.StatusPartialContent, codes.Unset, false}, {http.StatusMultiStatus, codes.Unset, false}, {http.StatusAlreadyReported, codes.Unset, false}, {http.StatusIMUsed, codes.Unset, false}, {http.StatusMultipleChoices, codes.Unset, false}, {http.StatusMovedPermanently, codes.Unset, false}, {http.StatusFound, codes.Unset, false}, {http.StatusSeeOther, codes.Unset, false}, {http.StatusNotModified, codes.Unset, false}, {http.StatusUseProxy, codes.Unset, false}, {306, codes.Unset, false}, {http.StatusTemporaryRedirect, codes.Unset, false}, {http.StatusPermanentRedirect, codes.Unset, false}, {http.StatusBadRequest, codes.Error, false}, {http.StatusUnauthorized, codes.Error, false}, {http.StatusPaymentRequired, codes.Error, false}, {http.StatusForbidden, codes.Error, false}, {http.StatusNotFound, codes.Error, false}, {http.StatusMethodNotAllowed, codes.Error, false}, {http.StatusNotAcceptable, codes.Error, false}, {http.StatusProxyAuthRequired, codes.Error, false}, {http.StatusRequestTimeout, codes.Error, false}, {http.StatusConflict, codes.Error, false}, {http.StatusGone, codes.Error, false}, {http.StatusLengthRequired, codes.Error, false}, {http.StatusPreconditionFailed, codes.Error, false}, {http.StatusRequestEntityTooLarge, codes.Error, false}, {http.StatusRequestURITooLong, codes.Error, false}, {http.StatusUnsupportedMediaType, codes.Error, false}, {http.StatusRequestedRangeNotSatisfiable, codes.Error, false}, {http.StatusExpectationFailed, codes.Error, false}, {http.StatusTeapot, codes.Error, false}, {http.StatusMisdirectedRequest, codes.Error, false}, {http.StatusUnprocessableEntity, codes.Error, false}, {http.StatusLocked, codes.Error, false}, {http.StatusFailedDependency, codes.Error, false}, {http.StatusTooEarly, codes.Error, false}, {http.StatusUpgradeRequired, codes.Error, false}, {http.StatusPreconditionRequired, codes.Error, false}, {http.StatusTooManyRequests, codes.Error, false}, {http.StatusRequestHeaderFieldsTooLarge, codes.Error, false}, {http.StatusUnavailableForLegalReasons, codes.Error, false}, {499, codes.Error, false}, {http.StatusInternalServerError, codes.Error, false}, {http.StatusNotImplemented, codes.Error, false}, {http.StatusBadGateway, codes.Error, false}, {http.StatusServiceUnavailable, codes.Error, false}, {http.StatusGatewayTimeout, codes.Error, false}, {http.StatusHTTPVersionNotSupported, codes.Error, false}, {http.StatusVariantAlsoNegotiates, codes.Error, false}, {http.StatusInsufficientStorage, codes.Error, false}, {http.StatusLoopDetected, codes.Error, false}, {http.StatusNotExtended, codes.Error, false}, {http.StatusNetworkAuthenticationRequired, codes.Error, false}, {600, codes.Error, true}, } for _, test := range tests { t.Run(strconv.Itoa(test.code), func(t *testing.T) { c, msg := HTTPClientStatus(test.code) assert.Equal(t, test.stat, c) if test.msg && msg == "" { t.Errorf("expected non-empty message for %d", test.code) } else if !test.msg && msg != "" { t.Errorf("expected empty message for %d, got: %s", test.code, msg) } }) } } func TestHTTPServerStatus(t *testing.T) { tests := []struct { code int stat codes.Code msg bool }{ {0, codes.Error, true}, {http.StatusContinue, codes.Unset, false}, {http.StatusSwitchingProtocols, codes.Unset, false}, {http.StatusProcessing, codes.Unset, false}, {http.StatusEarlyHints, codes.Unset, false}, {http.StatusOK, codes.Unset, false}, {http.StatusCreated, codes.Unset, false}, {http.StatusAccepted, codes.Unset, false}, {http.StatusNonAuthoritativeInfo, codes.Unset, false}, {http.StatusNoContent, codes.Unset, false}, {http.StatusResetContent, codes.Unset, false}, {http.StatusPartialContent, codes.Unset, false}, {http.StatusMultiStatus, codes.Unset, false}, {http.StatusAlreadyReported, codes.Unset, false}, {http.StatusIMUsed, codes.Unset, false}, {http.StatusMultipleChoices, codes.Unset, false}, {http.StatusMovedPermanently, codes.Unset, false}, {http.StatusFound, codes.Unset, false}, {http.StatusSeeOther, codes.Unset, false}, {http.StatusNotModified, codes.Unset, false}, {http.StatusUseProxy, codes.Unset, false}, {306, codes.Unset, false}, {http.StatusTemporaryRedirect, codes.Unset, false}, {http.StatusPermanentRedirect, codes.Unset, false}, {http.StatusBadRequest, codes.Unset, false}, {http.StatusUnauthorized, codes.Unset, false}, {http.StatusPaymentRequired, codes.Unset, false}, {http.StatusForbidden, codes.Unset, false}, {http.StatusNotFound, codes.Unset, false}, {http.StatusMethodNotAllowed, codes.Unset, false}, {http.StatusNotAcceptable, codes.Unset, false}, {http.StatusProxyAuthRequired, codes.Unset, false}, {http.StatusRequestTimeout, codes.Unset, false}, {http.StatusConflict, codes.Unset, false}, {http.StatusGone, codes.Unset, false}, {http.StatusLengthRequired, codes.Unset, false}, {http.StatusPreconditionFailed, codes.Unset, false}, {http.StatusRequestEntityTooLarge, codes.Unset, false}, {http.StatusRequestURITooLong, codes.Unset, false}, {http.StatusUnsupportedMediaType, codes.Unset, false}, {http.StatusRequestedRangeNotSatisfiable, codes.Unset, false}, {http.StatusExpectationFailed, codes.Unset, false}, {http.StatusTeapot, codes.Unset, false}, {http.StatusMisdirectedRequest, codes.Unset, false}, {http.StatusUnprocessableEntity, codes.Unset, false}, {http.StatusLocked, codes.Unset, false}, {http.StatusFailedDependency, codes.Unset, false}, {http.StatusTooEarly, codes.Unset, false}, {http.StatusUpgradeRequired, codes.Unset, false}, {http.StatusPreconditionRequired, codes.Unset, false}, {http.StatusTooManyRequests, codes.Unset, false}, {http.StatusRequestHeaderFieldsTooLarge, codes.Unset, false}, {http.StatusUnavailableForLegalReasons, codes.Unset, false}, {499, codes.Unset, false}, {http.StatusInternalServerError, codes.Error, false}, {http.StatusNotImplemented, codes.Error, false}, {http.StatusBadGateway, codes.Error, false}, {http.StatusServiceUnavailable, codes.Error, false}, {http.StatusGatewayTimeout, codes.Error, false}, {http.StatusHTTPVersionNotSupported, codes.Error, false}, {http.StatusVariantAlsoNegotiates, codes.Error, false}, {http.StatusInsufficientStorage, codes.Error, false}, {http.StatusLoopDetected, codes.Error, false}, {http.StatusNotExtended, codes.Error, false}, {http.StatusNetworkAuthenticationRequired, codes.Error, false}, {600, codes.Error, true}, } for _, test := range tests { c, msg := HTTPServerStatus(test.code) assert.Equal(t, test.stat, c) if test.msg && msg == "" { t.Errorf("expected non-empty message for %d", test.code) } else if !test.msg && msg != "" { t.Errorf("expected empty message for %d, got: %s", test.code, msg) } } } netconv.go000066400000000000000000000122661470323427300417620ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconvutil// Code created by gotmpl. DO NOT MODIFY. // source: internal/shared/semconvutil/netconv.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconvutil" import ( "net" "strconv" "strings" "go.opentelemetry.io/otel/attribute" semconv "go.opentelemetry.io/otel/semconv/v1.20.0" ) // NetTransport returns a trace attribute describing the transport protocol of the // passed network. See the net.Dial for information about acceptable network // values. func NetTransport(network string) attribute.KeyValue { return nc.Transport(network) } // netConv are the network semantic convention attributes defined for a version // of the OpenTelemetry specification. type netConv struct { NetHostNameKey attribute.Key NetHostPortKey attribute.Key NetPeerNameKey attribute.Key NetPeerPortKey attribute.Key NetProtocolName attribute.Key NetProtocolVersion attribute.Key NetSockFamilyKey attribute.Key NetSockPeerAddrKey attribute.Key NetSockPeerPortKey attribute.Key NetSockHostAddrKey attribute.Key NetSockHostPortKey attribute.Key NetTransportOther attribute.KeyValue NetTransportTCP attribute.KeyValue NetTransportUDP attribute.KeyValue NetTransportInProc attribute.KeyValue } var nc = &netConv{ NetHostNameKey: semconv.NetHostNameKey, NetHostPortKey: semconv.NetHostPortKey, NetPeerNameKey: semconv.NetPeerNameKey, NetPeerPortKey: semconv.NetPeerPortKey, NetProtocolName: semconv.NetProtocolNameKey, NetProtocolVersion: semconv.NetProtocolVersionKey, NetSockFamilyKey: semconv.NetSockFamilyKey, NetSockPeerAddrKey: semconv.NetSockPeerAddrKey, NetSockPeerPortKey: semconv.NetSockPeerPortKey, NetSockHostAddrKey: semconv.NetSockHostAddrKey, NetSockHostPortKey: semconv.NetSockHostPortKey, NetTransportOther: semconv.NetTransportOther, NetTransportTCP: semconv.NetTransportTCP, NetTransportUDP: semconv.NetTransportUDP, NetTransportInProc: semconv.NetTransportInProc, } func (c *netConv) Transport(network string) attribute.KeyValue { switch network { case "tcp", "tcp4", "tcp6": return c.NetTransportTCP case "udp", "udp4", "udp6": return c.NetTransportUDP case "unix", "unixgram", "unixpacket": return c.NetTransportInProc default: // "ip:*", "ip4:*", and "ip6:*" all are considered other. return c.NetTransportOther } } // Host returns attributes for a network host address. func (c *netConv) Host(address string) []attribute.KeyValue { h, p := splitHostPort(address) var n int if h != "" { n++ if p > 0 { n++ } } if n == 0 { return nil } attrs := make([]attribute.KeyValue, 0, n) attrs = append(attrs, c.HostName(h)) if p > 0 { attrs = append(attrs, c.HostPort(p)) } return attrs } func (c *netConv) HostName(name string) attribute.KeyValue { return c.NetHostNameKey.String(name) } func (c *netConv) HostPort(port int) attribute.KeyValue { return c.NetHostPortKey.Int(port) } func family(network, address string) string { switch network { case "unix", "unixgram", "unixpacket": return "unix" default: if ip := net.ParseIP(address); ip != nil { if ip.To4() == nil { return "inet6" } return "inet" } } return "" } // Peer returns attributes for a network peer address. func (c *netConv) Peer(address string) []attribute.KeyValue { h, p := splitHostPort(address) var n int if h != "" { n++ if p > 0 { n++ } } if n == 0 { return nil } attrs := make([]attribute.KeyValue, 0, n) attrs = append(attrs, c.PeerName(h)) if p > 0 { attrs = append(attrs, c.PeerPort(p)) } return attrs } func (c *netConv) PeerName(name string) attribute.KeyValue { return c.NetPeerNameKey.String(name) } func (c *netConv) PeerPort(port int) attribute.KeyValue { return c.NetPeerPortKey.Int(port) } func (c *netConv) SockPeerAddr(addr string) attribute.KeyValue { return c.NetSockPeerAddrKey.String(addr) } func (c *netConv) SockPeerPort(port int) attribute.KeyValue { return c.NetSockPeerPortKey.Int(port) } // splitHostPort splits a network address hostport of the form "host", // "host%zone", "[host]", "[host%zone], "host:port", "host%zone:port", // "[host]:port", "[host%zone]:port", or ":port" into host or host%zone and // port. // // An empty host is returned if it is not provided or unparsable. A negative // port is returned if it is not provided or unparsable. func splitHostPort(hostport string) (host string, port int) { port = -1 if strings.HasPrefix(hostport, "[") { addrEnd := strings.LastIndex(hostport, "]") if addrEnd < 0 { // Invalid hostport. return } if i := strings.LastIndex(hostport[addrEnd:], ":"); i < 0 { host = hostport[1:addrEnd] return } } else { if i := strings.LastIndex(hostport, ":"); i < 0 { host = hostport return } } host, pStr, err := net.SplitHostPort(hostport) if err != nil { return } p, err := strconv.ParseUint(pStr, 10, 16) if err != nil { return } return host, int(p) // nolint: gosec // Bitsize checked to be 16 above. } func netProtocol(proto string) (name string, version string) { name, version, _ = strings.Cut(proto, "/") name = strings.ToLower(name) return name, version } netconv_test.go000066400000000000000000000130111470323427300430060ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconvutil// Code created by gotmpl. DO NOT MODIFY. // source: internal/shared/semconvutil/netconv_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconvutil import ( "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/attribute" ) const ( addr = "127.0.0.1" port = 1834 ) func TestNetTransport(t *testing.T) { transports := map[string]attribute.KeyValue{ "tcp": attribute.String("net.transport", "ip_tcp"), "tcp4": attribute.String("net.transport", "ip_tcp"), "tcp6": attribute.String("net.transport", "ip_tcp"), "udp": attribute.String("net.transport", "ip_udp"), "udp4": attribute.String("net.transport", "ip_udp"), "udp6": attribute.String("net.transport", "ip_udp"), "unix": attribute.String("net.transport", "inproc"), "unixgram": attribute.String("net.transport", "inproc"), "unixpacket": attribute.String("net.transport", "inproc"), "ip:1": attribute.String("net.transport", "other"), "ip:icmp": attribute.String("net.transport", "other"), "ip4:proto": attribute.String("net.transport", "other"), "ip6:proto": attribute.String("net.transport", "other"), } for network, want := range transports { assert.Equal(t, want, NetTransport(network)) } } func TestNetHost(t *testing.T) { testAddrs(t, []addrTest{ {address: "", expected: nil}, {address: "192.0.0.1", expected: []attribute.KeyValue{ nc.HostName("192.0.0.1"), }}, {address: "192.0.0.1:9090", expected: []attribute.KeyValue{ nc.HostName("192.0.0.1"), nc.HostPort(9090), }}, }, nc.Host) } func TestNetHostName(t *testing.T) { expected := attribute.Key("net.host.name").String(addr) assert.Equal(t, expected, nc.HostName(addr)) } func TestNetHostPort(t *testing.T) { expected := attribute.Key("net.host.port").Int(port) assert.Equal(t, expected, nc.HostPort(port)) } func TestNetPeer(t *testing.T) { testAddrs(t, []addrTest{ {address: "", expected: nil}, {address: "example.com", expected: []attribute.KeyValue{ nc.PeerName("example.com"), }}, {address: "/tmp/file", expected: []attribute.KeyValue{ nc.PeerName("/tmp/file"), }}, {address: "192.0.0.1", expected: []attribute.KeyValue{ nc.PeerName("192.0.0.1"), }}, {address: ":9090", expected: nil}, {address: "192.0.0.1:9090", expected: []attribute.KeyValue{ nc.PeerName("192.0.0.1"), nc.PeerPort(9090), }}, }, nc.Peer) } func TestNetPeerName(t *testing.T) { expected := attribute.Key("net.peer.name").String(addr) assert.Equal(t, expected, nc.PeerName(addr)) } func TestNetPeerPort(t *testing.T) { expected := attribute.Key("net.peer.port").Int(port) assert.Equal(t, expected, nc.PeerPort(port)) } func TestNetSockPeerName(t *testing.T) { expected := attribute.Key("net.sock.peer.addr").String(addr) assert.Equal(t, expected, nc.SockPeerAddr(addr)) } func TestNetSockPeerPort(t *testing.T) { expected := attribute.Key("net.sock.peer.port").Int(port) assert.Equal(t, expected, nc.SockPeerPort(port)) } func TestNetFamily(t *testing.T) { tests := []struct { network string address string expect string }{ {"", "", ""}, {"unix", "", "unix"}, {"unix", "gibberish", "unix"}, {"unixgram", "", "unix"}, {"unixgram", "gibberish", "unix"}, {"unixpacket", "gibberish", "unix"}, {"tcp", "123.0.2.8", "inet"}, {"tcp", "gibberish", ""}, {"", "123.0.2.8", "inet"}, {"", "gibberish", ""}, {"tcp", "fe80::1", "inet6"}, {"", "fe80::1", "inet6"}, } for _, test := range tests { got := family(test.network, test.address) assert.Equal(t, test.expect, got, test.network+"/"+test.address) } } func TestSplitHostPort(t *testing.T) { tests := []struct { hostport string host string port int }{ {"", "", -1}, {":8080", "", 8080}, {"127.0.0.1", "127.0.0.1", -1}, {"www.example.com", "www.example.com", -1}, {"127.0.0.1%25en0", "127.0.0.1%25en0", -1}, {"[]", "", -1}, // Ensure this doesn't panic. {"[fe80::1", "", -1}, {"[fe80::1]", "fe80::1", -1}, {"[fe80::1%25en0]", "fe80::1%25en0", -1}, {"[fe80::1]:8080", "fe80::1", 8080}, {"[fe80::1]::", "", -1}, // Too many colons. {"127.0.0.1:", "127.0.0.1", -1}, {"127.0.0.1:port", "127.0.0.1", -1}, {"127.0.0.1:8080", "127.0.0.1", 8080}, {"www.example.com:8080", "www.example.com", 8080}, {"127.0.0.1%25en0:8080", "127.0.0.1%25en0", 8080}, } for _, test := range tests { h, p := splitHostPort(test.hostport) assert.Equal(t, test.host, h, test.hostport) assert.Equal(t, test.port, p, test.hostport) } } type addrTest struct { address string expected []attribute.KeyValue } func testAddrs(t *testing.T, tests []addrTest, f func(string) []attribute.KeyValue) { t.Helper() for _, test := range tests { got := f(test.address) assert.Equal(t, cap(test.expected), cap(got), "slice capacity") assert.ElementsMatch(t, test.expected, got, test.address) } } func TestNetProtocol(t *testing.T) { type testCase struct { name, version string } tests := map[string]testCase{ "HTTP/1.0": {name: "http", version: "1.0"}, "HTTP/1.1": {name: "http", version: "1.1"}, "HTTP/2": {name: "http", version: "2"}, "HTTP/3": {name: "http", version: "3"}, "SPDY": {name: "spdy"}, "SPDY/2": {name: "spdy", version: "2"}, "QUIC": {name: "quic"}, "unknown/proto/2": {name: "unknown", version: "proto/2"}, "other": {name: "other"}, } for proto, want := range tests { name, version := netProtocol(proto) assert.Equal(t, want.name, name) assert.Equal(t, want.version, version) } } option.go000066400000000000000000000056361470323427300354350ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gin-gonic/gin/otelgin// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Based on https://github.com/DataDog/dd-trace-go/blob/8fb554ff7cf694267f9077ae35e27ce4689ed8b6/contrib/gin-gonic/gin/option.go package otelgin // import "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin" import ( "net/http" "github.com/gin-gonic/gin" "go.opentelemetry.io/otel/propagation" oteltrace "go.opentelemetry.io/otel/trace" ) type config struct { TracerProvider oteltrace.TracerProvider Propagators propagation.TextMapPropagator Filters []Filter GinFilters []GinFilter SpanNameFormatter SpanNameFormatter } // Filter is a predicate used to determine whether a given http.request should // be traced. A Filter must return true if the request should be traced. type Filter func(*http.Request) bool // Adding new Filter parameter (*gin.Context) // gin.Context has FullPath() method, which returns a matched route full path. type GinFilter func(*gin.Context) bool // SpanNameFormatter is used to set span name by http.request. type SpanNameFormatter func(r *http.Request) string // Option specifies instrumentation configuration options. type Option interface { apply(*config) } type optionFunc func(*config) func (o optionFunc) apply(c *config) { o(c) } // WithPropagators specifies propagators to use for extracting // information from the HTTP requests. If none are specified, global // ones will be used. func WithPropagators(propagators propagation.TextMapPropagator) Option { return optionFunc(func(cfg *config) { if propagators != nil { cfg.Propagators = propagators } }) } // WithTracerProvider specifies a tracer provider to use for creating a tracer. // If none is specified, the global provider is used. func WithTracerProvider(provider oteltrace.TracerProvider) Option { return optionFunc(func(cfg *config) { if provider != nil { cfg.TracerProvider = provider } }) } // WithFilter adds a filter to the list of filters used by the handler. // If any filter indicates to exclude a request then the request will not be // traced. All gin and net/http filters must allow a request to be traced for a Span to be created. // If no filters are provided then all requests are traced. // Filters will be invoked for each processed request, it is advised to make them // simple and fast. func WithFilter(f ...Filter) Option { return optionFunc(func(c *config) { c.Filters = append(c.Filters, f...) }) } // WithGinFilter adds a gin filter to the list of filters used by the handler. func WithGinFilter(f ...GinFilter) Option { return optionFunc(func(c *config) { c.GinFilters = append(c.GinFilters, f...) }) } // WithSpanNameFormatter takes a function that will be called on every // request and the returned string will become the Span Name. func WithSpanNameFormatter(f func(r *http.Request) string) Option { return optionFunc(func(c *config) { c.SpanNameFormatter = f }) } test/000077500000000000000000000000001470323427300345435ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gin-gonic/gin/otelgindoc.go000066400000000000000000000006701470323427300356420ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gin-gonic/gin/otelgin/test// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 /* Package test validates the otelgin instrumentation with the default SDK. This package is in a separate module from the instrumentation it tests to isolate the dependency of the default SDK and not impose this as a transitive dependency for users. */ package test // import "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin/test" gintrace_test.go000066400000000000000000000250121470323427300377250ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gin-gonic/gin/otelgin/test// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Based on https://github.com/DataDog/dd-trace-go/blob/8fb554ff7cf694267f9077ae35e27ce4689ed8b6/contrib/gin-gonic/gin/gintrace_test.go package test import ( "errors" "html/template" "net/http" "net/http/httptest" "strconv" "testing" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/codes" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/sdk/trace/tracetest" "go.opentelemetry.io/otel/attribute" oteltrace "go.opentelemetry.io/otel/trace" ) func init() { gin.SetMode(gin.ReleaseMode) // silence annoying log msgs } func TestChildSpanFromGlobalTracer(t *testing.T) { sr := tracetest.NewSpanRecorder() otel.SetTracerProvider(sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr))) router := gin.New() router.Use(otelgin.Middleware("foobar")) router.GET("/user/:id", func(c *gin.Context) {}) r := httptest.NewRequest("GET", "/user/123", nil) w := httptest.NewRecorder() router.ServeHTTP(w, r) assert.Len(t, sr.Ended(), 1) } func TestChildSpanFromCustomTracer(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr)) router := gin.New() router.Use(otelgin.Middleware("foobar", otelgin.WithTracerProvider(provider))) router.GET("/user/:id", func(c *gin.Context) {}) r := httptest.NewRequest("GET", "/user/123", nil) w := httptest.NewRecorder() router.ServeHTTP(w, r) assert.Len(t, sr.Ended(), 1) } func TestTrace200(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr)) router := gin.New() router.Use(otelgin.Middleware("foobar", otelgin.WithTracerProvider(provider))) router.GET("/user/:id", func(c *gin.Context) { id := c.Param("id") _, _ = c.Writer.Write([]byte(id)) }) r := httptest.NewRequest("GET", "/user/123", nil) w := httptest.NewRecorder() // do and verify the request router.ServeHTTP(w, r) response := w.Result() //nolint:bodyclose // False positive for httptest.ResponseRecorder: https://github.com/timakin/bodyclose/issues/59. require.Equal(t, http.StatusOK, response.StatusCode) // verify traces look good spans := sr.Ended() require.Len(t, spans, 1) span := spans[0] assert.Equal(t, "/user/:id", span.Name()) assert.Equal(t, oteltrace.SpanKindServer, span.SpanKind()) attr := span.Attributes() assert.Contains(t, attr, attribute.String("net.host.name", "foobar")) assert.Contains(t, attr, attribute.Int("http.status_code", http.StatusOK)) assert.Contains(t, attr, attribute.String("http.method", "GET")) assert.Contains(t, attr, attribute.String("http.route", "/user/:id")) } func TestError(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr)) // setup router := gin.New() router.Use(otelgin.Middleware("foobar", otelgin.WithTracerProvider(provider))) // configure a handler that returns an error and 5xx status // code router.GET("/server_err", func(c *gin.Context) { _ = c.AbortWithError(http.StatusInternalServerError, errors.New("oh no")) }) r := httptest.NewRequest("GET", "/server_err", nil) w := httptest.NewRecorder() router.ServeHTTP(w, r) response := w.Result() //nolint:bodyclose // False positive for httptest.ResponseRecorder: https://github.com/timakin/bodyclose/issues/59. assert.Equal(t, http.StatusInternalServerError, response.StatusCode) // verify the errors and status are correct spans := sr.Ended() require.Len(t, spans, 1) span := spans[0] assert.Equal(t, "/server_err", span.Name()) attr := span.Attributes() assert.Contains(t, attr, attribute.String("net.host.name", "foobar")) assert.Contains(t, attr, attribute.Int("http.status_code", http.StatusInternalServerError)) assert.Contains(t, attr, attribute.String("gin.errors", "Error #01: oh no\n")) // server errors set the status assert.Equal(t, codes.Error, span.Status().Code) } func TestSpanStatus(t *testing.T) { testCases := []struct { httpStatusCode int wantSpanStatus codes.Code }{ {http.StatusOK, codes.Unset}, {http.StatusBadRequest, codes.Unset}, {http.StatusInternalServerError, codes.Error}, } for _, tc := range testCases { t.Run(strconv.Itoa(tc.httpStatusCode), func(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider() provider.RegisterSpanProcessor(sr) router := gin.New() router.Use(otelgin.Middleware("foobar", otelgin.WithTracerProvider(provider))) router.GET("/", func(c *gin.Context) { c.Status(tc.httpStatusCode) }) router.ServeHTTP(httptest.NewRecorder(), httptest.NewRequest("GET", "/", nil)) require.Len(t, sr.Ended(), 1, "should emit a span") assert.Equal(t, tc.wantSpanStatus, sr.Ended()[0].Status().Code, "should only set Error status for HTTP statuses >= 500") }) } } func TestSpanName(t *testing.T) { testCases := []struct { requestPath string spanNameFormatter otelgin.SpanNameFormatter wantSpanName string }{ {"/user/1", nil, "/user/:id"}, {"/user/1", func(r *http.Request) string { return r.URL.Path }, "/user/1"}, } for _, tc := range testCases { t.Run(tc.requestPath, func(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider() provider.RegisterSpanProcessor(sr) router := gin.New() router.Use(otelgin.Middleware("foobar", otelgin.WithTracerProvider(provider), otelgin.WithSpanNameFormatter(tc.spanNameFormatter))) router.GET("/user/:id", func(c *gin.Context) {}) router.ServeHTTP(httptest.NewRecorder(), httptest.NewRequest("GET", tc.requestPath, nil)) require.Len(t, sr.Ended(), 1, "should emit a span") assert.Equal(t, tc.wantSpanName, sr.Ended()[0].Name(), "span name not correct") }) } } func TestHTTPRouteWithSpanNameFormatter(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr)) router := gin.New() router.Use(otelgin.Middleware("foobar", otelgin.WithTracerProvider(provider), otelgin.WithSpanNameFormatter(func(r *http.Request) string { return r.URL.Path }), ), ) router.GET("/user/:id", func(c *gin.Context) { id := c.Param("id") _, _ = c.Writer.Write([]byte(id)) }) r := httptest.NewRequest("GET", "/user/123", nil) w := httptest.NewRecorder() // do and verify the request router.ServeHTTP(w, r) response := w.Result() //nolint:bodyclose // False positive for httptest.ResponseRecorder: https://github.com/timakin/bodyclose/issues/59. require.Equal(t, http.StatusOK, response.StatusCode) // verify traces look good spans := sr.Ended() require.Len(t, spans, 1) span := spans[0] assert.Equal(t, "/user/123", span.Name()) assert.Equal(t, oteltrace.SpanKindServer, span.SpanKind()) attr := span.Attributes() assert.Contains(t, attr, attribute.String("http.method", "GET")) assert.Contains(t, attr, attribute.String("http.route", "/user/:id")) } func TestHTML(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr)) // setup router := gin.New() router.Use(otelgin.Middleware("foobar", otelgin.WithTracerProvider(provider))) // add a template tmpl := template.Must(template.New("hello").Parse("hello {{.}}")) router.SetHTMLTemplate(tmpl) // a handler with an error and make the requests router.GET("/hello", func(c *gin.Context) { otelgin.HTML(c, http.StatusOK, "hello", "world") }) r := httptest.NewRequest("GET", "/hello", nil) w := httptest.NewRecorder() router.ServeHTTP(w, r) response := w.Result() //nolint:bodyclose // False positive for httptest.ResponseRecorder: https://github.com/timakin/bodyclose/issues/59. assert.Equal(t, http.StatusOK, response.StatusCode) assert.Equal(t, "hello world", w.Body.String()) // verify the errors and status are correct spans := sr.Ended() require.Len(t, spans, 2) var tspan sdktrace.ReadOnlySpan for _, s := range spans { // we need to pick up the span we're searching for, as the // order is not guaranteed within the buffer if s.Name() == "gin.renderer.html" { tspan = s break } } require.NotNil(t, tspan) assert.Contains(t, tspan.Attributes(), attribute.String("go.template", "hello")) } func TestWithFilter(t *testing.T) { t.Run("custom filter filtering route", func(t *testing.T) { sr := tracetest.NewSpanRecorder() otel.SetTracerProvider(sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr))) router := gin.New() f := func(req *http.Request) bool { return req.URL.Path != "/healthcheck" } router.Use(otelgin.Middleware("foobar", otelgin.WithFilter(f))) router.GET("/healthcheck", func(c *gin.Context) {}) r := httptest.NewRequest("GET", "/healthcheck", nil) w := httptest.NewRecorder() router.ServeHTTP(w, r) assert.Empty(t, sr.Ended()) }) t.Run("custom filter not filtering route", func(t *testing.T) { sr := tracetest.NewSpanRecorder() otel.SetTracerProvider(sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr))) router := gin.New() f := func(req *http.Request) bool { return req.URL.Path != "/healthcheck" } router.Use(otelgin.Middleware("foobar", otelgin.WithFilter(f))) router.GET("/user/:id", func(c *gin.Context) {}) r := httptest.NewRequest("GET", "/user/123", nil) w := httptest.NewRecorder() router.ServeHTTP(w, r) assert.Len(t, sr.Ended(), 1) }) } func TestWithGinFilter(t *testing.T) { t.Run("custom filter filtering route", func(t *testing.T) { sr := tracetest.NewSpanRecorder() otel.SetTracerProvider(sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr))) router := gin.New() f := func(c *gin.Context) bool { return c.Request.URL.Path != "/healthcheck" } router.Use(otelgin.Middleware("foobar", otelgin.WithGinFilter(f))) router.GET("/healthcheck", func(c *gin.Context) {}) r := httptest.NewRequest("GET", "/healthcheck", nil) w := httptest.NewRecorder() router.ServeHTTP(w, r) assert.Empty(t, sr.Ended()) }) t.Run("custom filter not filtering route", func(t *testing.T) { sr := tracetest.NewSpanRecorder() otel.SetTracerProvider(sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr))) router := gin.New() f := func(c *gin.Context) bool { return c.Request.URL.Path != "/user/:id" } router.Use(otelgin.Middleware("foobar", otelgin.WithGinFilter(f))) router.GET("/user/:id", func(c *gin.Context) {}) r := httptest.NewRequest("GET", "/user/123", nil) w := httptest.NewRecorder() router.ServeHTTP(w, r) assert.Len(t, sr.Ended(), 1) }) } go.mod000066400000000000000000000041371470323427300356560ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gin-gonic/gin/otelgin/testmodule go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin/test go 1.22 require ( github.com/gin-gonic/gin v1.10.0 github.com/stretchr/testify v1.9.0 go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.56.0 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/sdk v1.31.0 go.opentelemetry.io/otel/trace v1.31.0 ) require ( github.com/bytedance/sonic v1.12.3 // indirect github.com/bytedance/sonic/loader v0.2.0 // indirect github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/iasm v0.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/gabriel-vasile/mimetype v1.4.5 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.22.1 // indirect github.com/goccy/go-json v0.10.3 // indirect github.com/google/uuid v1.6.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.8 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect golang.org/x/arch v0.11.0 // indirect golang.org/x/crypto v0.28.0 // indirect golang.org/x/net v0.30.0 // indirect golang.org/x/sys v0.26.0 // indirect golang.org/x/text v0.19.0 // indirect google.golang.org/protobuf v1.35.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) replace go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin => ../ replace go.opentelemetry.io/contrib/propagators/b3 => ../../../../../../propagators/b3 go.sum000066400000000000000000000210721470323427300357000ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gin-gonic/gin/otelgin/testgithub.com/bytedance/sonic v1.12.3 h1:W2MGa7RCU1QTeYRTPE3+88mVC0yXmsRQRChiyVocVjU= github.com/bytedance/sonic v1.12.3/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/bytedance/sonic/loader v0.2.0 h1:zNprn+lsIP06C/IqCHs3gPQIvnvpKbbxyXQP1iU4kWM= github.com/bytedance/sonic/loader v0.2.0/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4= github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA= github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= golang.org/x/arch v0.11.0 h1:KXV8WWKCXm6tRpLirl2szsO5j/oOODwZf4hATmGVNs4= golang.org/x/arch v0.11.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= version.go000066400000000000000000000010471470323427300365610ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gin-gonic/gin/otelgin/test// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package test // import "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin/test" // Version is the current release version of the gin instrumentation test module. func Version() string { return "0.56.0" // This string is updated by the pre_release.sh script during release } // SemVersion is the semantic version to be supplied to tracer/meter creation. // // Deprecated: Use [Version] instead. func SemVersion() string { return Version() } version.go000066400000000000000000000010311470323427300355730ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gin-gonic/gin/otelgin// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelgin // import "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin" // Version is the current release version of the gin instrumentation. func Version() string { return "0.56.0" // This string is updated by the pre_release.sh script during release } // SemVersion is the semantic version to be supplied to tracer/meter creation. // // Deprecated: Use [Version] instead. func SemVersion() string { return Version() } open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gorilla/000077500000000000000000000000001470323427300312045ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gorilla/mux/000077500000000000000000000000001470323427300320155ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gorilla/mux/otelmux/000077500000000000000000000000001470323427300335125ustar00rootroot00000000000000config.go000066400000000000000000000064421470323427300352350ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gorilla/mux/otelmux// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelmux // import "go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux" import ( "net/http" "go.opentelemetry.io/otel/propagation" oteltrace "go.opentelemetry.io/otel/trace" ) // config is used to configure the mux middleware. type config struct { TracerProvider oteltrace.TracerProvider Propagators propagation.TextMapPropagator spanNameFormatter func(string, *http.Request) string PublicEndpoint bool PublicEndpointFn func(*http.Request) bool Filters []Filter } // Option specifies instrumentation configuration options. type Option interface { apply(*config) } type optionFunc func(*config) func (o optionFunc) apply(c *config) { o(c) } // Filter is a predicate used to determine whether a given http.request should // be traced. A Filter must return true if the request should be traced. type Filter func(*http.Request) bool // WithPublicEndpoint configures the Handler to link the span with an incoming // span context. If this option is not provided, then the association is a child // association instead of a link. func WithPublicEndpoint() Option { return optionFunc(func(c *config) { c.PublicEndpoint = true }) } // WithPublicEndpointFn runs with every request, and allows conditionally // configuring the Handler to link the span with an incoming span context. If // this option is not provided or returns false, then the association is a // child association instead of a link. // Note: WithPublicEndpoint takes precedence over WithPublicEndpointFn. func WithPublicEndpointFn(fn func(*http.Request) bool) Option { return optionFunc(func(c *config) { c.PublicEndpointFn = fn }) } // WithPropagators specifies propagators to use for extracting // information from the HTTP requests. If none are specified, global // ones will be used. func WithPropagators(propagators propagation.TextMapPropagator) Option { return optionFunc(func(cfg *config) { if propagators != nil { cfg.Propagators = propagators } }) } // WithTracerProvider specifies a tracer provider to use for creating a tracer. // If none is specified, the global provider is used. func WithTracerProvider(provider oteltrace.TracerProvider) Option { return optionFunc(func(cfg *config) { if provider != nil { cfg.TracerProvider = provider } }) } // WithSpanNameFormatter specifies a function to use for generating a custom span // name. By default, the route name (path template or regexp) is used. The route // name is provided so you can use it in the span name without needing to // duplicate the logic for extracting it from the request. func WithSpanNameFormatter(fn func(routeName string, r *http.Request) string) Option { return optionFunc(func(cfg *config) { cfg.spanNameFormatter = fn }) } // WithFilter adds a filter to the list of filters used by the handler. // If any filter indicates to exclude a request then the request will not be // traced. All filters must allow a request to be traced for a Span to be created. // If no filters are provided then all requests are traced. // Filters will be invoked for each processed request, it is advised to make them // simple and fast. func WithFilter(f Filter) Option { return optionFunc(func(c *config) { c.Filters = append(c.Filters, f) }) } doc.go000066400000000000000000000005571470323427300345360ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gorilla/mux/otelmux// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package otelmux instruments the github.com/gorilla/mux package. // // Currently only the routing of a received message can be instrumented. To do // it, use the Middleware function. package otelmux // import "go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux" example/000077500000000000000000000000001470323427300350665ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gorilla/mux/otelmuxDockerfile000066400000000000000000000004031470323427300370550ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gorilla/mux/otelmux/example# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 FROM golang:alpine AS base COPY . /src/ WORKDIR /src/instrumentation/github.com/gorilla/mux/otelmux/example FROM base AS mux-server RUN go install ./server.go CMD ["/go/bin/server"] README.md000066400000000000000000000012731470323427300363500ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gorilla/mux/otelmux/example# gorilla/mux instrumentation example An HTTP server using gorilla/mux and instrumentation. The server has a `/users/{id:[0-9]+}` endpoint. The server generates span information to `stdout`. These instructions expect you have [docker-compose](https://docs.docker.com/compose/) installed. Bring up the `mux-server` and `mux-client` services to run the example: ```sh docker-compose up --detach mux-server mux-client ``` The `mux-client` service sends just one HTTP request to `mux-server` and then exits. View the span generated by `mux-server` in the logs: ```sh docker-compose logs mux-server ``` Shut down the services when you are finished with the example: ```sh docker-compose down ``` docker-compose.yml000066400000000000000000000010451470323427300405230ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gorilla/mux/otelmux/example# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 version: "3.7" services: mux-client: image: golang:alpine networks: - example command: - "/bin/sh" - "-c" - "wget http://mux-server:8080/users/123 && cat 123" depends_on: - mux-server mux-server: build: dockerfile: $PWD/Dockerfile context: ../../../../../.. ports: - "8080:80" command: - "/bin/sh" - "-c" - "/go/bin/server" networks: - example networks: example: go.mod000066400000000000000000000014111470323427300361710ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gorilla/mux/otelmux/examplemodule go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux/example go 1.22 replace go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux => ../ require ( github.com/gorilla/mux v1.8.1 go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.56.0 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0 go.opentelemetry.io/otel/sdk v1.31.0 go.opentelemetry.io/otel/trace v1.31.0 ) require ( github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect golang.org/x/sys v0.26.0 // indirect ) go.sum000066400000000000000000000054361470323427300362310ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gorilla/mux/otelmux/examplegithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0 h1:UGZ1QwZWY67Z6BmckTU+9Rxn04m2bD3gD6Mk0OIOCPk= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0/go.mod h1:fcwWuDuaObkkChiDlhEpSq9+X1C0omv+s5mBtToAQ64= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= server.go000066400000000000000000000036261470323427300367320ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gorilla/mux/otelmux/example// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package main import ( "context" "fmt" "log" "net/http" "github.com/gorilla/mux" "go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/propagation" sdktrace "go.opentelemetry.io/otel/sdk/trace" oteltrace "go.opentelemetry.io/otel/trace" ) var tracer = otel.Tracer("mux-server") func main() { tp, err := initTracer() if err != nil { log.Fatal(err) } defer func() { if err := tp.Shutdown(context.Background()); err != nil { log.Printf("Error shutting down tracer provider: %v", err) } }() r := mux.NewRouter() r.Use(otelmux.Middleware("my-server")) r.HandleFunc("/users/{id:[0-9]+}", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) id := vars["id"] name := getUser(r.Context(), id) reply := fmt.Sprintf("user %s (id %s)\n", name, id) _, _ = w.Write(([]byte)(reply)) })) http.Handle("/", r) _ = http.ListenAndServe(":8080", nil) //nolint:gosec // Ignoring G114: Use of net/http serve function that has no support for setting timeouts. } func initTracer() (*sdktrace.TracerProvider, error) { exporter, err := stdout.New(stdout.WithPrettyPrint()) if err != nil { return nil, err } tp := sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithBatcher(exporter), ) otel.SetTracerProvider(tp) otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) return tp, nil } func getUser(ctx context.Context, id string) string { _, span := tracer.Start(ctx, "getUser", oteltrace.WithAttributes(attribute.String("id", id))) defer span.End() if id == "123" { return "otelmux tester" } return "unknown" } go.mod000066400000000000000000000010721470323427300345410ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gorilla/mux/otelmuxmodule go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux go 1.22 require ( github.com/felixge/httpsnoop v1.0.4 github.com/gorilla/mux v1.8.1 github.com/stretchr/testify v1.9.0 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/trace v1.31.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) go.sum000066400000000000000000000044371470323427300345760ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gorilla/mux/otelmuxgithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= internal/000077500000000000000000000000001470323427300352475ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gorilla/mux/otelmuxsemconvutil/000077500000000000000000000000001470323427300376175ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gorilla/mux/otelmux/internalgen.go000066400000000000000000000013611470323427300407200ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gorilla/mux/otelmux/internal/semconvutil// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux/internal/semconvutil" // Generate semconvutil package: //go:generate gotmpl --body=../../../../../../../internal/shared/semconvutil/httpconv_test.go.tmpl "--data={}" --out=httpconv_test.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconvutil/httpconv.go.tmpl "--data={}" --out=httpconv.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconvutil/netconv_test.go.tmpl "--data={}" --out=netconv_test.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconvutil/netconv.go.tmpl "--data={}" --out=netconv.go httpconv.go000066400000000000000000000466161470323427300420300ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gorilla/mux/otelmux/internal/semconvutil// Code created by gotmpl. DO NOT MODIFY. // source: internal/shared/semconvutil/httpconv.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux/internal/semconvutil" import ( "fmt" "net/http" "strings" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" semconv "go.opentelemetry.io/otel/semconv/v1.20.0" ) // HTTPClientResponse returns trace attributes for an HTTP response received by a // client from a server. It will return the following attributes if the related // values are defined in resp: "http.status.code", // "http.response_content_length". // // This does not add all OpenTelemetry required attributes for an HTTP event, // it assumes ClientRequest was used to create the span with a complete set of // attributes. If a complete set of attributes can be generated using the // request contained in resp. For example: // // append(HTTPClientResponse(resp), ClientRequest(resp.Request)...) func HTTPClientResponse(resp *http.Response) []attribute.KeyValue { return hc.ClientResponse(resp) } // HTTPClientRequest returns trace attributes for an HTTP request made by a client. // The following attributes are always returned: "http.url", "http.method", // "net.peer.name". The following attributes are returned if the related values // are defined in req: "net.peer.port", "user_agent.original", // "http.request_content_length". func HTTPClientRequest(req *http.Request) []attribute.KeyValue { return hc.ClientRequest(req) } // HTTPClientRequestMetrics returns metric attributes for an HTTP request made by a client. // The following attributes are always returned: "http.method", "net.peer.name". // The following attributes are returned if the // related values are defined in req: "net.peer.port". func HTTPClientRequestMetrics(req *http.Request) []attribute.KeyValue { return hc.ClientRequestMetrics(req) } // HTTPClientStatus returns a span status code and message for an HTTP status code // value received by a client. func HTTPClientStatus(code int) (codes.Code, string) { return hc.ClientStatus(code) } // HTTPServerRequest returns trace attributes for an HTTP request received by a // server. // // The server must be the primary server name if it is known. For example this // would be the ServerName directive // (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache // server, and the server_name directive // (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an // nginx server. More generically, the primary server name would be the host // header value that matches the default virtual host of an HTTP server. It // should include the host identifier and if a port is used to route to the // server that port identifier should be included as an appropriate port // suffix. // // If the primary server name is not known, server should be an empty string. // The req Host will be used to determine the server instead. // // The following attributes are always returned: "http.method", "http.scheme", // "http.target", "net.host.name". The following attributes are returned if // they related values are defined in req: "net.host.port", "net.sock.peer.addr", // "net.sock.peer.port", "user_agent.original", "http.client_ip". func HTTPServerRequest(server string, req *http.Request) []attribute.KeyValue { return hc.ServerRequest(server, req) } // HTTPServerRequestMetrics returns metric attributes for an HTTP request received by a // server. // // The server must be the primary server name if it is known. For example this // would be the ServerName directive // (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache // server, and the server_name directive // (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an // nginx server. More generically, the primary server name would be the host // header value that matches the default virtual host of an HTTP server. It // should include the host identifier and if a port is used to route to the // server that port identifier should be included as an appropriate port // suffix. // // If the primary server name is not known, server should be an empty string. // The req Host will be used to determine the server instead. // // The following attributes are always returned: "http.method", "http.scheme", // "net.host.name". The following attributes are returned if they related // values are defined in req: "net.host.port". func HTTPServerRequestMetrics(server string, req *http.Request) []attribute.KeyValue { return hc.ServerRequestMetrics(server, req) } // HTTPServerStatus returns a span status code and message for an HTTP status code // value returned by a server. Status codes in the 400-499 range are not // returned as errors. func HTTPServerStatus(code int) (codes.Code, string) { return hc.ServerStatus(code) } // httpConv are the HTTP semantic convention attributes defined for a version // of the OpenTelemetry specification. type httpConv struct { NetConv *netConv HTTPClientIPKey attribute.Key HTTPMethodKey attribute.Key HTTPRequestContentLengthKey attribute.Key HTTPResponseContentLengthKey attribute.Key HTTPRouteKey attribute.Key HTTPSchemeHTTP attribute.KeyValue HTTPSchemeHTTPS attribute.KeyValue HTTPStatusCodeKey attribute.Key HTTPTargetKey attribute.Key HTTPURLKey attribute.Key UserAgentOriginalKey attribute.Key } var hc = &httpConv{ NetConv: nc, HTTPClientIPKey: semconv.HTTPClientIPKey, HTTPMethodKey: semconv.HTTPMethodKey, HTTPRequestContentLengthKey: semconv.HTTPRequestContentLengthKey, HTTPResponseContentLengthKey: semconv.HTTPResponseContentLengthKey, HTTPRouteKey: semconv.HTTPRouteKey, HTTPSchemeHTTP: semconv.HTTPSchemeHTTP, HTTPSchemeHTTPS: semconv.HTTPSchemeHTTPS, HTTPStatusCodeKey: semconv.HTTPStatusCodeKey, HTTPTargetKey: semconv.HTTPTargetKey, HTTPURLKey: semconv.HTTPURLKey, UserAgentOriginalKey: semconv.UserAgentOriginalKey, } // ClientResponse returns attributes for an HTTP response received by a client // from a server. The following attributes are returned if the related values // are defined in resp: "http.status.code", "http.response_content_length". // // This does not add all OpenTelemetry required attributes for an HTTP event, // it assumes ClientRequest was used to create the span with a complete set of // attributes. If a complete set of attributes can be generated using the // request contained in resp. For example: // // append(ClientResponse(resp), ClientRequest(resp.Request)...) func (c *httpConv) ClientResponse(resp *http.Response) []attribute.KeyValue { /* The following semantic conventions are returned if present: http.status_code int http.response_content_length int */ var n int if resp.StatusCode > 0 { n++ } if resp.ContentLength > 0 { n++ } attrs := make([]attribute.KeyValue, 0, n) if resp.StatusCode > 0 { attrs = append(attrs, c.HTTPStatusCodeKey.Int(resp.StatusCode)) } if resp.ContentLength > 0 { attrs = append(attrs, c.HTTPResponseContentLengthKey.Int(int(resp.ContentLength))) } return attrs } // ClientRequest returns attributes for an HTTP request made by a client. The // following attributes are always returned: "http.url", "http.method", // "net.peer.name". The following attributes are returned if the related values // are defined in req: "net.peer.port", "user_agent.original", // "http.request_content_length", "user_agent.original". func (c *httpConv) ClientRequest(req *http.Request) []attribute.KeyValue { /* The following semantic conventions are returned if present: http.method string user_agent.original string http.url string net.peer.name string net.peer.port int http.request_content_length int */ /* The following semantic conventions are not returned: http.status_code This requires the response. See ClientResponse. http.response_content_length This requires the response. See ClientResponse. net.sock.family This requires the socket used. net.sock.peer.addr This requires the socket used. net.sock.peer.name This requires the socket used. net.sock.peer.port This requires the socket used. http.resend_count This is something outside of a single request. net.protocol.name The value is the Request is ignored, and the go client will always use "http". net.protocol.version The value in the Request is ignored, and the go client will always use 1.1 or 2.0. */ n := 3 // URL, peer name, proto, and method. var h string if req.URL != nil { h = req.URL.Host } peer, p := firstHostPort(h, req.Header.Get("Host")) port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", p) if port > 0 { n++ } useragent := req.UserAgent() if useragent != "" { n++ } if req.ContentLength > 0 { n++ } attrs := make([]attribute.KeyValue, 0, n) attrs = append(attrs, c.method(req.Method)) var u string if req.URL != nil { // Remove any username/password info that may be in the URL. userinfo := req.URL.User req.URL.User = nil u = req.URL.String() // Restore any username/password info that was removed. req.URL.User = userinfo } attrs = append(attrs, c.HTTPURLKey.String(u)) attrs = append(attrs, c.NetConv.PeerName(peer)) if port > 0 { attrs = append(attrs, c.NetConv.PeerPort(port)) } if useragent != "" { attrs = append(attrs, c.UserAgentOriginalKey.String(useragent)) } if l := req.ContentLength; l > 0 { attrs = append(attrs, c.HTTPRequestContentLengthKey.Int64(l)) } return attrs } // ClientRequestMetrics returns metric attributes for an HTTP request made by a client. The // following attributes are always returned: "http.method", "net.peer.name". // The following attributes are returned if the related values // are defined in req: "net.peer.port". func (c *httpConv) ClientRequestMetrics(req *http.Request) []attribute.KeyValue { /* The following semantic conventions are returned if present: http.method string net.peer.name string net.peer.port int */ n := 2 // method, peer name. var h string if req.URL != nil { h = req.URL.Host } peer, p := firstHostPort(h, req.Header.Get("Host")) port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", p) if port > 0 { n++ } attrs := make([]attribute.KeyValue, 0, n) attrs = append(attrs, c.method(req.Method), c.NetConv.PeerName(peer)) if port > 0 { attrs = append(attrs, c.NetConv.PeerPort(port)) } return attrs } // ServerRequest returns attributes for an HTTP request received by a server. // // The server must be the primary server name if it is known. For example this // would be the ServerName directive // (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache // server, and the server_name directive // (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an // nginx server. More generically, the primary server name would be the host // header value that matches the default virtual host of an HTTP server. It // should include the host identifier and if a port is used to route to the // server that port identifier should be included as an appropriate port // suffix. // // If the primary server name is not known, server should be an empty string. // The req Host will be used to determine the server instead. // // The following attributes are always returned: "http.method", "http.scheme", // "http.target", "net.host.name". The following attributes are returned if they // related values are defined in req: "net.host.port", "net.sock.peer.addr", // "net.sock.peer.port", "user_agent.original", "http.client_ip", // "net.protocol.name", "net.protocol.version". func (c *httpConv) ServerRequest(server string, req *http.Request) []attribute.KeyValue { /* The following semantic conventions are returned if present: http.method string http.scheme string net.host.name string net.host.port int net.sock.peer.addr string net.sock.peer.port int user_agent.original string http.client_ip string net.protocol.name string Note: not set if the value is "http". net.protocol.version string http.target string Note: doesn't include the query parameter. */ /* The following semantic conventions are not returned: http.status_code This requires the response. http.request_content_length This requires the len() of body, which can mutate it. http.response_content_length This requires the response. http.route This is not available. net.sock.peer.name This would require a DNS lookup. net.sock.host.addr The request doesn't have access to the underlying socket. net.sock.host.port The request doesn't have access to the underlying socket. */ n := 4 // Method, scheme, proto, and host name. var host string var p int if server == "" { host, p = splitHostPort(req.Host) } else { // Prioritize the primary server name. host, p = splitHostPort(server) if p < 0 { _, p = splitHostPort(req.Host) } } hostPort := requiredHTTPPort(req.TLS != nil, p) if hostPort > 0 { n++ } peer, peerPort := splitHostPort(req.RemoteAddr) if peer != "" { n++ if peerPort > 0 { n++ } } useragent := req.UserAgent() if useragent != "" { n++ } clientIP := serverClientIP(req.Header.Get("X-Forwarded-For")) if clientIP != "" { n++ } var target string if req.URL != nil { target = req.URL.Path if target != "" { n++ } } protoName, protoVersion := netProtocol(req.Proto) if protoName != "" && protoName != "http" { n++ } if protoVersion != "" { n++ } attrs := make([]attribute.KeyValue, 0, n) attrs = append(attrs, c.method(req.Method)) attrs = append(attrs, c.scheme(req.TLS != nil)) attrs = append(attrs, c.NetConv.HostName(host)) if hostPort > 0 { attrs = append(attrs, c.NetConv.HostPort(hostPort)) } if peer != "" { // The Go HTTP server sets RemoteAddr to "IP:port", this will not be a // file-path that would be interpreted with a sock family. attrs = append(attrs, c.NetConv.SockPeerAddr(peer)) if peerPort > 0 { attrs = append(attrs, c.NetConv.SockPeerPort(peerPort)) } } if useragent != "" { attrs = append(attrs, c.UserAgentOriginalKey.String(useragent)) } if clientIP != "" { attrs = append(attrs, c.HTTPClientIPKey.String(clientIP)) } if target != "" { attrs = append(attrs, c.HTTPTargetKey.String(target)) } if protoName != "" && protoName != "http" { attrs = append(attrs, c.NetConv.NetProtocolName.String(protoName)) } if protoVersion != "" { attrs = append(attrs, c.NetConv.NetProtocolVersion.String(protoVersion)) } return attrs } // ServerRequestMetrics returns metric attributes for an HTTP request received // by a server. // // The server must be the primary server name if it is known. For example this // would be the ServerName directive // (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache // server, and the server_name directive // (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an // nginx server. More generically, the primary server name would be the host // header value that matches the default virtual host of an HTTP server. It // should include the host identifier and if a port is used to route to the // server that port identifier should be included as an appropriate port // suffix. // // If the primary server name is not known, server should be an empty string. // The req Host will be used to determine the server instead. // // The following attributes are always returned: "http.method", "http.scheme", // "net.host.name". The following attributes are returned if they related // values are defined in req: "net.host.port". func (c *httpConv) ServerRequestMetrics(server string, req *http.Request) []attribute.KeyValue { /* The following semantic conventions are returned if present: http.scheme string http.route string http.method string http.status_code int net.host.name string net.host.port int net.protocol.name string Note: not set if the value is "http". net.protocol.version string */ n := 3 // Method, scheme, and host name. var host string var p int if server == "" { host, p = splitHostPort(req.Host) } else { // Prioritize the primary server name. host, p = splitHostPort(server) if p < 0 { _, p = splitHostPort(req.Host) } } hostPort := requiredHTTPPort(req.TLS != nil, p) if hostPort > 0 { n++ } protoName, protoVersion := netProtocol(req.Proto) if protoName != "" { n++ } if protoVersion != "" { n++ } attrs := make([]attribute.KeyValue, 0, n) attrs = append(attrs, c.methodMetric(req.Method)) attrs = append(attrs, c.scheme(req.TLS != nil)) attrs = append(attrs, c.NetConv.HostName(host)) if hostPort > 0 { attrs = append(attrs, c.NetConv.HostPort(hostPort)) } if protoName != "" { attrs = append(attrs, c.NetConv.NetProtocolName.String(protoName)) } if protoVersion != "" { attrs = append(attrs, c.NetConv.NetProtocolVersion.String(protoVersion)) } return attrs } func (c *httpConv) method(method string) attribute.KeyValue { if method == "" { return c.HTTPMethodKey.String(http.MethodGet) } return c.HTTPMethodKey.String(method) } func (c *httpConv) methodMetric(method string) attribute.KeyValue { method = strings.ToUpper(method) switch method { case http.MethodConnect, http.MethodDelete, http.MethodGet, http.MethodHead, http.MethodOptions, http.MethodPatch, http.MethodPost, http.MethodPut, http.MethodTrace: default: method = "_OTHER" } return c.HTTPMethodKey.String(method) } func (c *httpConv) scheme(https bool) attribute.KeyValue { // nolint:revive if https { return c.HTTPSchemeHTTPS } return c.HTTPSchemeHTTP } func serverClientIP(xForwardedFor string) string { if idx := strings.Index(xForwardedFor, ","); idx >= 0 { xForwardedFor = xForwardedFor[:idx] } return xForwardedFor } func requiredHTTPPort(https bool, port int) int { // nolint:revive if https { if port > 0 && port != 443 { return port } } else { if port > 0 && port != 80 { return port } } return -1 } // Return the request host and port from the first non-empty source. func firstHostPort(source ...string) (host string, port int) { for _, hostport := range source { host, port = splitHostPort(hostport) if host != "" || port > 0 { break } } return } // ClientStatus returns a span status code and message for an HTTP status code // value received by a client. func (c *httpConv) ClientStatus(code int) (codes.Code, string) { if code < 100 || code >= 600 { return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) } if code >= 400 { return codes.Error, "" } return codes.Unset, "" } // ServerStatus returns a span status code and message for an HTTP status code // value returned by a server. Status codes in the 400-499 range are not // returned as errors. func (c *httpConv) ServerStatus(code int) (codes.Code, string) { if code < 100 || code >= 600 { return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) } if code >= 500 { return codes.Error, "" } return codes.Unset, "" } httpconv_test.go000066400000000000000000000372171470323427300430640ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gorilla/mux/otelmux/internal/semconvutil// Code created by gotmpl. DO NOT MODIFY. // source: internal/shared/semconvutil/httpconv_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconvutil import ( "net/http" "net/http/httptest" "net/url" "strconv" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" ) func TestHTTPClientResponse(t *testing.T) { const stat, n = 201, 397 resp := &http.Response{ StatusCode: stat, ContentLength: n, } got := HTTPClientResponse(resp) assert.Equal(t, 2, cap(got), "slice capacity") assert.ElementsMatch(t, []attribute.KeyValue{ attribute.Key("http.status_code").Int(stat), attribute.Key("http.response_content_length").Int(n), }, got) } func TestHTTPSClientRequest(t *testing.T) { req := &http.Request{ Method: http.MethodGet, URL: &url.URL{ Scheme: "https", Host: "127.0.0.1:443", Path: "/resource", }, Proto: "HTTP/1.0", ProtoMajor: 1, ProtoMinor: 0, } assert.ElementsMatch( t, []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.url", "https://127.0.0.1:443/resource"), attribute.String("net.peer.name", "127.0.0.1"), }, HTTPClientRequest(req), ) } func TestHTTPSClientRequestMetrics(t *testing.T) { req := &http.Request{ Method: http.MethodGet, URL: &url.URL{ Scheme: "https", Host: "127.0.0.1:443", Path: "/resource", }, Proto: "HTTP/1.0", ProtoMajor: 1, ProtoMinor: 0, } assert.ElementsMatch( t, []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("net.peer.name", "127.0.0.1"), }, HTTPClientRequestMetrics(req), ) } func TestHTTPClientRequest(t *testing.T) { const ( user = "alice" n = 128 agent = "Go-http-client/1.1" ) req := &http.Request{ Method: http.MethodGet, URL: &url.URL{ Scheme: "http", Host: "127.0.0.1:8080", Path: "/resource", }, Proto: "HTTP/1.0", ProtoMajor: 1, ProtoMinor: 0, Header: http.Header{ "User-Agent": []string{agent}, }, ContentLength: n, } req.SetBasicAuth(user, "pswrd") assert.ElementsMatch( t, []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.url", "http://127.0.0.1:8080/resource"), attribute.String("net.peer.name", "127.0.0.1"), attribute.Int("net.peer.port", 8080), attribute.String("user_agent.original", agent), attribute.Int("http.request_content_length", n), }, HTTPClientRequest(req), ) } func TestHTTPClientRequestMetrics(t *testing.T) { const ( user = "alice" n = 128 agent = "Go-http-client/1.1" ) req := &http.Request{ Method: http.MethodGet, URL: &url.URL{ Scheme: "http", Host: "127.0.0.1:8080", Path: "/resource", }, Proto: "HTTP/1.0", ProtoMajor: 1, ProtoMinor: 0, Header: http.Header{ "User-Agent": []string{agent}, }, ContentLength: n, } req.SetBasicAuth(user, "pswrd") assert.ElementsMatch( t, []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("net.peer.name", "127.0.0.1"), attribute.Int("net.peer.port", 8080), }, HTTPClientRequestMetrics(req), ) } func TestHTTPClientRequestRequired(t *testing.T) { req := new(http.Request) var got []attribute.KeyValue assert.NotPanics(t, func() { got = HTTPClientRequest(req) }) want := []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.url", ""), attribute.String("net.peer.name", ""), } assert.Equal(t, want, got) } func TestHTTPServerRequest(t *testing.T) { got := make(chan *http.Request, 1) handler := func(w http.ResponseWriter, r *http.Request) { got <- r w.WriteHeader(http.StatusOK) } srv := httptest.NewServer(http.HandlerFunc(handler)) defer srv.Close() srvURL, err := url.Parse(srv.URL) require.NoError(t, err) srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) require.NoError(t, err) resp, err := srv.Client().Get(srv.URL) require.NoError(t, err) require.NoError(t, resp.Body.Close()) req := <-got peer, peerPort := splitHostPort(req.RemoteAddr) const user = "alice" req.SetBasicAuth(user, "pswrd") const clientIP = "127.0.0.5" req.Header.Add("X-Forwarded-For", clientIP) assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.scheme", "http"), attribute.String("net.host.name", srvURL.Hostname()), attribute.Int("net.host.port", int(srvPort)), attribute.String("net.sock.peer.addr", peer), attribute.Int("net.sock.peer.port", peerPort), attribute.String("user_agent.original", "Go-http-client/1.1"), attribute.String("http.client_ip", clientIP), attribute.String("net.protocol.version", "1.1"), attribute.String("http.target", "/"), }, HTTPServerRequest("", req)) } func TestHTTPServerRequestMetrics(t *testing.T) { got := make(chan *http.Request, 1) handler := func(w http.ResponseWriter, r *http.Request) { got <- r w.WriteHeader(http.StatusOK) } srv := httptest.NewServer(http.HandlerFunc(handler)) defer srv.Close() srvURL, err := url.Parse(srv.URL) require.NoError(t, err) srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) require.NoError(t, err) resp, err := srv.Client().Get(srv.URL) require.NoError(t, err) require.NoError(t, resp.Body.Close()) req := <-got assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.scheme", "http"), attribute.String("net.host.name", srvURL.Hostname()), attribute.Int("net.host.port", int(srvPort)), attribute.String("net.protocol.name", "http"), attribute.String("net.protocol.version", "1.1"), }, HTTPServerRequestMetrics("", req)) } func TestHTTPServerName(t *testing.T) { req := new(http.Request) var got []attribute.KeyValue const ( host = "test.semconv.server" port = 8080 ) portStr := strconv.Itoa(port) server := host + ":" + portStr assert.NotPanics(t, func() { got = HTTPServerRequest(server, req) }) assert.Contains(t, got, attribute.String("net.host.name", host)) assert.Contains(t, got, attribute.Int("net.host.port", port)) req = &http.Request{Host: "alt.host.name:" + portStr} // The server parameter does not include a port, ServerRequest should use // the port in the request Host field. assert.NotPanics(t, func() { got = HTTPServerRequest(host, req) }) assert.Contains(t, got, attribute.String("net.host.name", host)) assert.Contains(t, got, attribute.Int("net.host.port", port)) } func TestHTTPServerRequestFailsGracefully(t *testing.T) { req := new(http.Request) var got []attribute.KeyValue assert.NotPanics(t, func() { got = HTTPServerRequest("", req) }) want := []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.scheme", "http"), attribute.String("net.host.name", ""), } assert.ElementsMatch(t, want, got) } func TestHTTPMethod(t *testing.T) { assert.Equal(t, attribute.String("http.method", "POST"), hc.method("POST")) assert.Equal(t, attribute.String("http.method", "GET"), hc.method("")) assert.Equal(t, attribute.String("http.method", "garbage"), hc.method("garbage")) } func TestHTTPScheme(t *testing.T) { assert.Equal(t, attribute.String("http.scheme", "http"), hc.scheme(false)) assert.Equal(t, attribute.String("http.scheme", "https"), hc.scheme(true)) } func TestHTTPServerClientIP(t *testing.T) { tests := []struct { xForwardedFor string want string }{ {"", ""}, {"127.0.0.1", "127.0.0.1"}, {"127.0.0.1,127.0.0.5", "127.0.0.1"}, } for _, test := range tests { got := serverClientIP(test.xForwardedFor) assert.Equal(t, test.want, got, test.xForwardedFor) } } func TestRequiredHTTPPort(t *testing.T) { tests := []struct { https bool port int want int }{ {true, 443, -1}, {true, 80, 80}, {true, 8081, 8081}, {false, 443, 443}, {false, 80, -1}, {false, 8080, 8080}, } for _, test := range tests { got := requiredHTTPPort(test.https, test.port) assert.Equal(t, test.want, got, test.https, test.port) } } func TestFirstHostPort(t *testing.T) { host, port := "127.0.0.1", 8080 hostport := "127.0.0.1:8080" sources := [][]string{ {hostport}, {"", hostport}, {"", "", hostport}, {"", "", hostport, ""}, {"", "", hostport, "127.0.0.3:80"}, } for _, src := range sources { h, p := firstHostPort(src...) assert.Equal(t, host, h, src) assert.Equal(t, port, p, src) } } func TestHTTPClientStatus(t *testing.T) { tests := []struct { code int stat codes.Code msg bool }{ {0, codes.Error, true}, {http.StatusContinue, codes.Unset, false}, {http.StatusSwitchingProtocols, codes.Unset, false}, {http.StatusProcessing, codes.Unset, false}, {http.StatusEarlyHints, codes.Unset, false}, {http.StatusOK, codes.Unset, false}, {http.StatusCreated, codes.Unset, false}, {http.StatusAccepted, codes.Unset, false}, {http.StatusNonAuthoritativeInfo, codes.Unset, false}, {http.StatusNoContent, codes.Unset, false}, {http.StatusResetContent, codes.Unset, false}, {http.StatusPartialContent, codes.Unset, false}, {http.StatusMultiStatus, codes.Unset, false}, {http.StatusAlreadyReported, codes.Unset, false}, {http.StatusIMUsed, codes.Unset, false}, {http.StatusMultipleChoices, codes.Unset, false}, {http.StatusMovedPermanently, codes.Unset, false}, {http.StatusFound, codes.Unset, false}, {http.StatusSeeOther, codes.Unset, false}, {http.StatusNotModified, codes.Unset, false}, {http.StatusUseProxy, codes.Unset, false}, {306, codes.Unset, false}, {http.StatusTemporaryRedirect, codes.Unset, false}, {http.StatusPermanentRedirect, codes.Unset, false}, {http.StatusBadRequest, codes.Error, false}, {http.StatusUnauthorized, codes.Error, false}, {http.StatusPaymentRequired, codes.Error, false}, {http.StatusForbidden, codes.Error, false}, {http.StatusNotFound, codes.Error, false}, {http.StatusMethodNotAllowed, codes.Error, false}, {http.StatusNotAcceptable, codes.Error, false}, {http.StatusProxyAuthRequired, codes.Error, false}, {http.StatusRequestTimeout, codes.Error, false}, {http.StatusConflict, codes.Error, false}, {http.StatusGone, codes.Error, false}, {http.StatusLengthRequired, codes.Error, false}, {http.StatusPreconditionFailed, codes.Error, false}, {http.StatusRequestEntityTooLarge, codes.Error, false}, {http.StatusRequestURITooLong, codes.Error, false}, {http.StatusUnsupportedMediaType, codes.Error, false}, {http.StatusRequestedRangeNotSatisfiable, codes.Error, false}, {http.StatusExpectationFailed, codes.Error, false}, {http.StatusTeapot, codes.Error, false}, {http.StatusMisdirectedRequest, codes.Error, false}, {http.StatusUnprocessableEntity, codes.Error, false}, {http.StatusLocked, codes.Error, false}, {http.StatusFailedDependency, codes.Error, false}, {http.StatusTooEarly, codes.Error, false}, {http.StatusUpgradeRequired, codes.Error, false}, {http.StatusPreconditionRequired, codes.Error, false}, {http.StatusTooManyRequests, codes.Error, false}, {http.StatusRequestHeaderFieldsTooLarge, codes.Error, false}, {http.StatusUnavailableForLegalReasons, codes.Error, false}, {499, codes.Error, false}, {http.StatusInternalServerError, codes.Error, false}, {http.StatusNotImplemented, codes.Error, false}, {http.StatusBadGateway, codes.Error, false}, {http.StatusServiceUnavailable, codes.Error, false}, {http.StatusGatewayTimeout, codes.Error, false}, {http.StatusHTTPVersionNotSupported, codes.Error, false}, {http.StatusVariantAlsoNegotiates, codes.Error, false}, {http.StatusInsufficientStorage, codes.Error, false}, {http.StatusLoopDetected, codes.Error, false}, {http.StatusNotExtended, codes.Error, false}, {http.StatusNetworkAuthenticationRequired, codes.Error, false}, {600, codes.Error, true}, } for _, test := range tests { t.Run(strconv.Itoa(test.code), func(t *testing.T) { c, msg := HTTPClientStatus(test.code) assert.Equal(t, test.stat, c) if test.msg && msg == "" { t.Errorf("expected non-empty message for %d", test.code) } else if !test.msg && msg != "" { t.Errorf("expected empty message for %d, got: %s", test.code, msg) } }) } } func TestHTTPServerStatus(t *testing.T) { tests := []struct { code int stat codes.Code msg bool }{ {0, codes.Error, true}, {http.StatusContinue, codes.Unset, false}, {http.StatusSwitchingProtocols, codes.Unset, false}, {http.StatusProcessing, codes.Unset, false}, {http.StatusEarlyHints, codes.Unset, false}, {http.StatusOK, codes.Unset, false}, {http.StatusCreated, codes.Unset, false}, {http.StatusAccepted, codes.Unset, false}, {http.StatusNonAuthoritativeInfo, codes.Unset, false}, {http.StatusNoContent, codes.Unset, false}, {http.StatusResetContent, codes.Unset, false}, {http.StatusPartialContent, codes.Unset, false}, {http.StatusMultiStatus, codes.Unset, false}, {http.StatusAlreadyReported, codes.Unset, false}, {http.StatusIMUsed, codes.Unset, false}, {http.StatusMultipleChoices, codes.Unset, false}, {http.StatusMovedPermanently, codes.Unset, false}, {http.StatusFound, codes.Unset, false}, {http.StatusSeeOther, codes.Unset, false}, {http.StatusNotModified, codes.Unset, false}, {http.StatusUseProxy, codes.Unset, false}, {306, codes.Unset, false}, {http.StatusTemporaryRedirect, codes.Unset, false}, {http.StatusPermanentRedirect, codes.Unset, false}, {http.StatusBadRequest, codes.Unset, false}, {http.StatusUnauthorized, codes.Unset, false}, {http.StatusPaymentRequired, codes.Unset, false}, {http.StatusForbidden, codes.Unset, false}, {http.StatusNotFound, codes.Unset, false}, {http.StatusMethodNotAllowed, codes.Unset, false}, {http.StatusNotAcceptable, codes.Unset, false}, {http.StatusProxyAuthRequired, codes.Unset, false}, {http.StatusRequestTimeout, codes.Unset, false}, {http.StatusConflict, codes.Unset, false}, {http.StatusGone, codes.Unset, false}, {http.StatusLengthRequired, codes.Unset, false}, {http.StatusPreconditionFailed, codes.Unset, false}, {http.StatusRequestEntityTooLarge, codes.Unset, false}, {http.StatusRequestURITooLong, codes.Unset, false}, {http.StatusUnsupportedMediaType, codes.Unset, false}, {http.StatusRequestedRangeNotSatisfiable, codes.Unset, false}, {http.StatusExpectationFailed, codes.Unset, false}, {http.StatusTeapot, codes.Unset, false}, {http.StatusMisdirectedRequest, codes.Unset, false}, {http.StatusUnprocessableEntity, codes.Unset, false}, {http.StatusLocked, codes.Unset, false}, {http.StatusFailedDependency, codes.Unset, false}, {http.StatusTooEarly, codes.Unset, false}, {http.StatusUpgradeRequired, codes.Unset, false}, {http.StatusPreconditionRequired, codes.Unset, false}, {http.StatusTooManyRequests, codes.Unset, false}, {http.StatusRequestHeaderFieldsTooLarge, codes.Unset, false}, {http.StatusUnavailableForLegalReasons, codes.Unset, false}, {499, codes.Unset, false}, {http.StatusInternalServerError, codes.Error, false}, {http.StatusNotImplemented, codes.Error, false}, {http.StatusBadGateway, codes.Error, false}, {http.StatusServiceUnavailable, codes.Error, false}, {http.StatusGatewayTimeout, codes.Error, false}, {http.StatusHTTPVersionNotSupported, codes.Error, false}, {http.StatusVariantAlsoNegotiates, codes.Error, false}, {http.StatusInsufficientStorage, codes.Error, false}, {http.StatusLoopDetected, codes.Error, false}, {http.StatusNotExtended, codes.Error, false}, {http.StatusNetworkAuthenticationRequired, codes.Error, false}, {600, codes.Error, true}, } for _, test := range tests { c, msg := HTTPServerStatus(test.code) assert.Equal(t, test.stat, c) if test.msg && msg == "" { t.Errorf("expected non-empty message for %d", test.code) } else if !test.msg && msg != "" { t.Errorf("expected empty message for %d, got: %s", test.code, msg) } } } netconv.go000066400000000000000000000122641470323427300416270ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gorilla/mux/otelmux/internal/semconvutil// Code created by gotmpl. DO NOT MODIFY. // source: internal/shared/semconvutil/netconv.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux/internal/semconvutil" import ( "net" "strconv" "strings" "go.opentelemetry.io/otel/attribute" semconv "go.opentelemetry.io/otel/semconv/v1.20.0" ) // NetTransport returns a trace attribute describing the transport protocol of the // passed network. See the net.Dial for information about acceptable network // values. func NetTransport(network string) attribute.KeyValue { return nc.Transport(network) } // netConv are the network semantic convention attributes defined for a version // of the OpenTelemetry specification. type netConv struct { NetHostNameKey attribute.Key NetHostPortKey attribute.Key NetPeerNameKey attribute.Key NetPeerPortKey attribute.Key NetProtocolName attribute.Key NetProtocolVersion attribute.Key NetSockFamilyKey attribute.Key NetSockPeerAddrKey attribute.Key NetSockPeerPortKey attribute.Key NetSockHostAddrKey attribute.Key NetSockHostPortKey attribute.Key NetTransportOther attribute.KeyValue NetTransportTCP attribute.KeyValue NetTransportUDP attribute.KeyValue NetTransportInProc attribute.KeyValue } var nc = &netConv{ NetHostNameKey: semconv.NetHostNameKey, NetHostPortKey: semconv.NetHostPortKey, NetPeerNameKey: semconv.NetPeerNameKey, NetPeerPortKey: semconv.NetPeerPortKey, NetProtocolName: semconv.NetProtocolNameKey, NetProtocolVersion: semconv.NetProtocolVersionKey, NetSockFamilyKey: semconv.NetSockFamilyKey, NetSockPeerAddrKey: semconv.NetSockPeerAddrKey, NetSockPeerPortKey: semconv.NetSockPeerPortKey, NetSockHostAddrKey: semconv.NetSockHostAddrKey, NetSockHostPortKey: semconv.NetSockHostPortKey, NetTransportOther: semconv.NetTransportOther, NetTransportTCP: semconv.NetTransportTCP, NetTransportUDP: semconv.NetTransportUDP, NetTransportInProc: semconv.NetTransportInProc, } func (c *netConv) Transport(network string) attribute.KeyValue { switch network { case "tcp", "tcp4", "tcp6": return c.NetTransportTCP case "udp", "udp4", "udp6": return c.NetTransportUDP case "unix", "unixgram", "unixpacket": return c.NetTransportInProc default: // "ip:*", "ip4:*", and "ip6:*" all are considered other. return c.NetTransportOther } } // Host returns attributes for a network host address. func (c *netConv) Host(address string) []attribute.KeyValue { h, p := splitHostPort(address) var n int if h != "" { n++ if p > 0 { n++ } } if n == 0 { return nil } attrs := make([]attribute.KeyValue, 0, n) attrs = append(attrs, c.HostName(h)) if p > 0 { attrs = append(attrs, c.HostPort(p)) } return attrs } func (c *netConv) HostName(name string) attribute.KeyValue { return c.NetHostNameKey.String(name) } func (c *netConv) HostPort(port int) attribute.KeyValue { return c.NetHostPortKey.Int(port) } func family(network, address string) string { switch network { case "unix", "unixgram", "unixpacket": return "unix" default: if ip := net.ParseIP(address); ip != nil { if ip.To4() == nil { return "inet6" } return "inet" } } return "" } // Peer returns attributes for a network peer address. func (c *netConv) Peer(address string) []attribute.KeyValue { h, p := splitHostPort(address) var n int if h != "" { n++ if p > 0 { n++ } } if n == 0 { return nil } attrs := make([]attribute.KeyValue, 0, n) attrs = append(attrs, c.PeerName(h)) if p > 0 { attrs = append(attrs, c.PeerPort(p)) } return attrs } func (c *netConv) PeerName(name string) attribute.KeyValue { return c.NetPeerNameKey.String(name) } func (c *netConv) PeerPort(port int) attribute.KeyValue { return c.NetPeerPortKey.Int(port) } func (c *netConv) SockPeerAddr(addr string) attribute.KeyValue { return c.NetSockPeerAddrKey.String(addr) } func (c *netConv) SockPeerPort(port int) attribute.KeyValue { return c.NetSockPeerPortKey.Int(port) } // splitHostPort splits a network address hostport of the form "host", // "host%zone", "[host]", "[host%zone], "host:port", "host%zone:port", // "[host]:port", "[host%zone]:port", or ":port" into host or host%zone and // port. // // An empty host is returned if it is not provided or unparsable. A negative // port is returned if it is not provided or unparsable. func splitHostPort(hostport string) (host string, port int) { port = -1 if strings.HasPrefix(hostport, "[") { addrEnd := strings.LastIndex(hostport, "]") if addrEnd < 0 { // Invalid hostport. return } if i := strings.LastIndex(hostport[addrEnd:], ":"); i < 0 { host = hostport[1:addrEnd] return } } else { if i := strings.LastIndex(hostport, ":"); i < 0 { host = hostport return } } host, pStr, err := net.SplitHostPort(hostport) if err != nil { return } p, err := strconv.ParseUint(pStr, 10, 16) if err != nil { return } return host, int(p) // nolint: gosec // Bitsize checked to be 16 above. } func netProtocol(proto string) (name string, version string) { name, version, _ = strings.Cut(proto, "/") name = strings.ToLower(name) return name, version } netconv_test.go000066400000000000000000000130111470323427300426550ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gorilla/mux/otelmux/internal/semconvutil// Code created by gotmpl. DO NOT MODIFY. // source: internal/shared/semconvutil/netconv_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconvutil import ( "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/attribute" ) const ( addr = "127.0.0.1" port = 1834 ) func TestNetTransport(t *testing.T) { transports := map[string]attribute.KeyValue{ "tcp": attribute.String("net.transport", "ip_tcp"), "tcp4": attribute.String("net.transport", "ip_tcp"), "tcp6": attribute.String("net.transport", "ip_tcp"), "udp": attribute.String("net.transport", "ip_udp"), "udp4": attribute.String("net.transport", "ip_udp"), "udp6": attribute.String("net.transport", "ip_udp"), "unix": attribute.String("net.transport", "inproc"), "unixgram": attribute.String("net.transport", "inproc"), "unixpacket": attribute.String("net.transport", "inproc"), "ip:1": attribute.String("net.transport", "other"), "ip:icmp": attribute.String("net.transport", "other"), "ip4:proto": attribute.String("net.transport", "other"), "ip6:proto": attribute.String("net.transport", "other"), } for network, want := range transports { assert.Equal(t, want, NetTransport(network)) } } func TestNetHost(t *testing.T) { testAddrs(t, []addrTest{ {address: "", expected: nil}, {address: "192.0.0.1", expected: []attribute.KeyValue{ nc.HostName("192.0.0.1"), }}, {address: "192.0.0.1:9090", expected: []attribute.KeyValue{ nc.HostName("192.0.0.1"), nc.HostPort(9090), }}, }, nc.Host) } func TestNetHostName(t *testing.T) { expected := attribute.Key("net.host.name").String(addr) assert.Equal(t, expected, nc.HostName(addr)) } func TestNetHostPort(t *testing.T) { expected := attribute.Key("net.host.port").Int(port) assert.Equal(t, expected, nc.HostPort(port)) } func TestNetPeer(t *testing.T) { testAddrs(t, []addrTest{ {address: "", expected: nil}, {address: "example.com", expected: []attribute.KeyValue{ nc.PeerName("example.com"), }}, {address: "/tmp/file", expected: []attribute.KeyValue{ nc.PeerName("/tmp/file"), }}, {address: "192.0.0.1", expected: []attribute.KeyValue{ nc.PeerName("192.0.0.1"), }}, {address: ":9090", expected: nil}, {address: "192.0.0.1:9090", expected: []attribute.KeyValue{ nc.PeerName("192.0.0.1"), nc.PeerPort(9090), }}, }, nc.Peer) } func TestNetPeerName(t *testing.T) { expected := attribute.Key("net.peer.name").String(addr) assert.Equal(t, expected, nc.PeerName(addr)) } func TestNetPeerPort(t *testing.T) { expected := attribute.Key("net.peer.port").Int(port) assert.Equal(t, expected, nc.PeerPort(port)) } func TestNetSockPeerName(t *testing.T) { expected := attribute.Key("net.sock.peer.addr").String(addr) assert.Equal(t, expected, nc.SockPeerAddr(addr)) } func TestNetSockPeerPort(t *testing.T) { expected := attribute.Key("net.sock.peer.port").Int(port) assert.Equal(t, expected, nc.SockPeerPort(port)) } func TestNetFamily(t *testing.T) { tests := []struct { network string address string expect string }{ {"", "", ""}, {"unix", "", "unix"}, {"unix", "gibberish", "unix"}, {"unixgram", "", "unix"}, {"unixgram", "gibberish", "unix"}, {"unixpacket", "gibberish", "unix"}, {"tcp", "123.0.2.8", "inet"}, {"tcp", "gibberish", ""}, {"", "123.0.2.8", "inet"}, {"", "gibberish", ""}, {"tcp", "fe80::1", "inet6"}, {"", "fe80::1", "inet6"}, } for _, test := range tests { got := family(test.network, test.address) assert.Equal(t, test.expect, got, test.network+"/"+test.address) } } func TestSplitHostPort(t *testing.T) { tests := []struct { hostport string host string port int }{ {"", "", -1}, {":8080", "", 8080}, {"127.0.0.1", "127.0.0.1", -1}, {"www.example.com", "www.example.com", -1}, {"127.0.0.1%25en0", "127.0.0.1%25en0", -1}, {"[]", "", -1}, // Ensure this doesn't panic. {"[fe80::1", "", -1}, {"[fe80::1]", "fe80::1", -1}, {"[fe80::1%25en0]", "fe80::1%25en0", -1}, {"[fe80::1]:8080", "fe80::1", 8080}, {"[fe80::1]::", "", -1}, // Too many colons. {"127.0.0.1:", "127.0.0.1", -1}, {"127.0.0.1:port", "127.0.0.1", -1}, {"127.0.0.1:8080", "127.0.0.1", 8080}, {"www.example.com:8080", "www.example.com", 8080}, {"127.0.0.1%25en0:8080", "127.0.0.1%25en0", 8080}, } for _, test := range tests { h, p := splitHostPort(test.hostport) assert.Equal(t, test.host, h, test.hostport) assert.Equal(t, test.port, p, test.hostport) } } type addrTest struct { address string expected []attribute.KeyValue } func testAddrs(t *testing.T, tests []addrTest, f func(string) []attribute.KeyValue) { t.Helper() for _, test := range tests { got := f(test.address) assert.Equal(t, cap(test.expected), cap(got), "slice capacity") assert.ElementsMatch(t, test.expected, got, test.address) } } func TestNetProtocol(t *testing.T) { type testCase struct { name, version string } tests := map[string]testCase{ "HTTP/1.0": {name: "http", version: "1.0"}, "HTTP/1.1": {name: "http", version: "1.1"}, "HTTP/2": {name: "http", version: "2"}, "HTTP/3": {name: "http", version: "3"}, "SPDY": {name: "spdy"}, "SPDY/2": {name: "spdy", version: "2"}, "QUIC": {name: "quic"}, "unknown/proto/2": {name: "unknown", version: "proto/2"}, "other": {name: "other"}, } for proto, want := range tests { name, version := netProtocol(proto) assert.Equal(t, want.name, name) assert.Equal(t, want.version, version) } } mux.go000066400000000000000000000113241470323427300345740ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gorilla/mux/otelmux// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelmux // import "go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux" import ( "fmt" "net/http" "sync" "github.com/felixge/httpsnoop" "github.com/gorilla/mux" "go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux/internal/semconvutil" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/propagation" semconv "go.opentelemetry.io/otel/semconv/v1.20.0" "go.opentelemetry.io/otel/trace" ) const ( // ScopeName is the instrumentation scope name. ScopeName = "go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux" ) // Middleware sets up a handler to start tracing the incoming // requests. The service parameter should describe the name of the // (virtual) server handling the request. func Middleware(service string, opts ...Option) mux.MiddlewareFunc { cfg := config{} for _, opt := range opts { opt.apply(&cfg) } if cfg.TracerProvider == nil { cfg.TracerProvider = otel.GetTracerProvider() } tracer := cfg.TracerProvider.Tracer( ScopeName, trace.WithInstrumentationVersion(Version()), ) if cfg.Propagators == nil { cfg.Propagators = otel.GetTextMapPropagator() } if cfg.spanNameFormatter == nil { cfg.spanNameFormatter = defaultSpanNameFunc } return func(handler http.Handler) http.Handler { return traceware{ service: service, tracer: tracer, propagators: cfg.Propagators, handler: handler, spanNameFormatter: cfg.spanNameFormatter, publicEndpoint: cfg.PublicEndpoint, publicEndpointFn: cfg.PublicEndpointFn, filters: cfg.Filters, } } } type traceware struct { service string tracer trace.Tracer propagators propagation.TextMapPropagator handler http.Handler spanNameFormatter func(string, *http.Request) string publicEndpoint bool publicEndpointFn func(*http.Request) bool filters []Filter } type recordingResponseWriter struct { writer http.ResponseWriter written bool status int } var rrwPool = &sync.Pool{ New: func() interface{} { return &recordingResponseWriter{} }, } func getRRW(writer http.ResponseWriter) *recordingResponseWriter { rrw := rrwPool.Get().(*recordingResponseWriter) rrw.written = false rrw.status = http.StatusOK rrw.writer = httpsnoop.Wrap(writer, httpsnoop.Hooks{ Write: func(next httpsnoop.WriteFunc) httpsnoop.WriteFunc { return func(b []byte) (int, error) { if !rrw.written { rrw.written = true } return next(b) } }, WriteHeader: func(next httpsnoop.WriteHeaderFunc) httpsnoop.WriteHeaderFunc { return func(statusCode int) { if !rrw.written { rrw.written = true rrw.status = statusCode } next(statusCode) } }, }) return rrw } func putRRW(rrw *recordingResponseWriter) { rrw.writer = nil rrwPool.Put(rrw) } // defaultSpanNameFunc just reuses the route name as the span name. func defaultSpanNameFunc(routeName string, _ *http.Request) string { return routeName } // ServeHTTP implements the http.Handler interface. It does the actual // tracing of the request. func (tw traceware) ServeHTTP(w http.ResponseWriter, r *http.Request) { for _, f := range tw.filters { if !f(r) { // Simply pass through to the handler if a filter rejects the request tw.handler.ServeHTTP(w, r) return } } ctx := tw.propagators.Extract(r.Context(), propagation.HeaderCarrier(r.Header)) routeStr := "" route := mux.CurrentRoute(r) if route != nil { var err error routeStr, err = route.GetPathTemplate() if err != nil { routeStr, err = route.GetPathRegexp() if err != nil { routeStr = "" } } } opts := []trace.SpanStartOption{ trace.WithAttributes(semconvutil.HTTPServerRequest(tw.service, r)...), trace.WithSpanKind(trace.SpanKindServer), } if tw.publicEndpoint || (tw.publicEndpointFn != nil && tw.publicEndpointFn(r.WithContext(ctx))) { opts = append(opts, trace.WithNewRoot()) // Linking incoming span context if any for public endpoint. if s := trace.SpanContextFromContext(ctx); s.IsValid() && s.IsRemote() { opts = append(opts, trace.WithLinks(trace.Link{SpanContext: s})) } } if routeStr == "" { routeStr = fmt.Sprintf("HTTP %s route not found", r.Method) } else { rAttr := semconv.HTTPRoute(routeStr) opts = append(opts, trace.WithAttributes(rAttr)) } spanName := tw.spanNameFormatter(routeStr, r) ctx, span := tw.tracer.Start(ctx, spanName, opts...) defer span.End() r2 := r.WithContext(ctx) rrw := getRRW(w) defer putRRW(rrw) tw.handler.ServeHTTP(rrw.writer, r2) if rrw.status > 0 { span.SetAttributes(semconv.HTTPStatusCode(rrw.status)) } span.SetStatus(semconvutil.HTTPServerStatus(rrw.status)) } mux_test.go000066400000000000000000000126311470323427300356350ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gorilla/mux/otelmux// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelmux import ( "bufio" "context" "io" "net" "net/http" "net/http/httptest" "testing" "github.com/gorilla/mux" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" ) var sc = trace.NewSpanContext(trace.SpanContextConfig{ TraceID: [16]byte{1}, SpanID: [8]byte{1}, Remote: true, TraceFlags: trace.FlagsSampled, }) func TestPassthroughSpanFromGlobalTracer(t *testing.T) { var called bool router := mux.NewRouter() router.Use(Middleware("foobar")) // The default global TracerProvider provides "pass through" spans for any // span context in the incoming request context. router.HandleFunc("/user/{id}", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { called = true got := trace.SpanFromContext(r.Context()).SpanContext() assert.Equal(t, sc, got) w.WriteHeader(http.StatusOK) })) r := httptest.NewRequest("GET", "/user/123", nil) r = r.WithContext(trace.ContextWithRemoteSpanContext(context.Background(), sc)) w := httptest.NewRecorder() router.ServeHTTP(w, r) assert.True(t, called, "failed to run test") } func TestPropagationWithGlobalPropagators(t *testing.T) { defer func(p propagation.TextMapPropagator) { otel.SetTextMapPropagator(p) }(otel.GetTextMapPropagator()) prop := propagation.TraceContext{} otel.SetTextMapPropagator(prop) r := httptest.NewRequest("GET", "/user/123", nil) w := httptest.NewRecorder() ctx := trace.ContextWithRemoteSpanContext(context.Background(), sc) otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(r.Header)) var called bool router := mux.NewRouter() router.Use(Middleware("foobar")) router.HandleFunc("/user/{id}", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { called = true span := trace.SpanFromContext(r.Context()) assert.Equal(t, sc, span.SpanContext()) w.WriteHeader(http.StatusOK) })) router.ServeHTTP(w, r) assert.True(t, called, "failed to run test") } func TestPropagationWithCustomPropagators(t *testing.T) { prop := propagation.TraceContext{} r := httptest.NewRequest("GET", "/user/123", nil) w := httptest.NewRecorder() ctx := trace.ContextWithRemoteSpanContext(context.Background(), sc) prop.Inject(ctx, propagation.HeaderCarrier(r.Header)) var called bool router := mux.NewRouter() router.Use(Middleware("foobar", WithPropagators(prop))) router.HandleFunc("/user/{id}", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { called = true span := trace.SpanFromContext(r.Context()) assert.Equal(t, sc, span.SpanContext()) w.WriteHeader(http.StatusOK) })) router.ServeHTTP(w, r) assert.True(t, called, "failed to run test") } type testResponseWriter struct { writer http.ResponseWriter } func (rw *testResponseWriter) Header() http.Header { return rw.writer.Header() } func (rw *testResponseWriter) Write(b []byte) (int, error) { return rw.writer.Write(b) } func (rw *testResponseWriter) WriteHeader(statusCode int) { rw.writer.WriteHeader(statusCode) } // implement Hijacker. func (rw *testResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { return nil, nil, nil } // implement Pusher. func (rw *testResponseWriter) Push(target string, opts *http.PushOptions) error { return nil } // implement Flusher. func (rw *testResponseWriter) Flush() { } // implement io.ReaderFrom. func (rw *testResponseWriter) ReadFrom(r io.Reader) (n int64, err error) { return 0, nil } func TestResponseWriterInterfaces(t *testing.T) { // make sure the recordingResponseWriter preserves interfaces implemented by the wrapped writer router := mux.NewRouter() router.Use(Middleware("foobar")) router.HandleFunc("/user/{id}", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Implements(t, (*http.Hijacker)(nil), w) assert.Implements(t, (*http.Pusher)(nil), w) assert.Implements(t, (*http.Flusher)(nil), w) assert.Implements(t, (*io.ReaderFrom)(nil), w) w.WriteHeader(http.StatusOK) })) r := httptest.NewRequest("GET", "/user/123", nil) w := &testResponseWriter{ writer: httptest.NewRecorder(), } router.ServeHTTP(w, r) } func TestFilter(t *testing.T) { prop := propagation.TraceContext{} router := mux.NewRouter() var calledHealth, calledTest int router.Use(Middleware("foobar", WithFilter(func(r *http.Request) bool { return r.URL.Path != "/health" }))) router.HandleFunc("/health", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { calledHealth++ span := trace.SpanFromContext(r.Context()) assert.NotEqual(t, sc, span.SpanContext()) w.WriteHeader(http.StatusOK) })) router.HandleFunc("/test", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { calledTest++ span := trace.SpanFromContext(r.Context()) assert.Equal(t, sc, span.SpanContext()) w.WriteHeader(http.StatusOK) })) r := httptest.NewRequest("GET", "/health", nil) ctx := trace.ContextWithRemoteSpanContext(context.Background(), sc) prop.Inject(ctx, propagation.HeaderCarrier(r.Header)) w := httptest.NewRecorder() router.ServeHTTP(w, r) r = httptest.NewRequest("GET", "/test", nil) ctx = trace.ContextWithRemoteSpanContext(context.Background(), sc) prop.Inject(ctx, propagation.HeaderCarrier(r.Header)) w = httptest.NewRecorder() router.ServeHTTP(w, r) assert.Equal(t, 1, calledHealth, "failed to run test") assert.Equal(t, 1, calledTest, "failed to run test") } open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gorilla/mux/otelmux/test/000077500000000000000000000000001470323427300344715ustar00rootroot00000000000000doc.go000066400000000000000000000006661470323427300355160ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gorilla/mux/otelmux/test// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 /* Package test validates the otelmux instrumentation with the default SDK. This package is in a separate module from the instrumentation it tests to isolate the dependency of the default SDK and not impose this as a transitive dependency for users. */ package test // import "go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux/test" go.mod000066400000000000000000000015611470323427300355230ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gorilla/mux/otelmux/testmodule go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux/test go 1.22 require ( github.com/gorilla/mux v1.8.1 github.com/stretchr/testify v1.9.0 go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.56.0 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/sdk v1.31.0 go.opentelemetry.io/otel/trace v1.31.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect golang.org/x/sys v0.26.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) replace go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux => ../ go.sum000066400000000000000000000054141470323427300355510ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gorilla/mux/otelmux/testgithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= mux_test.go000066400000000000000000000207571470323427300366240ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gorilla/mux/otelmux/test// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package test import ( "context" "fmt" "net/http" "net/http/httptest" "testing" "github.com/gorilla/mux" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/propagation" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/sdk/trace/tracetest" "go.opentelemetry.io/otel/trace" ) func TestCustomSpanNameFormatter(t *testing.T) { exporter := tracetest.NewInMemoryExporter() tp := sdktrace.NewTracerProvider(sdktrace.WithSyncer(exporter)) routeTpl := "/user/{id}" testdata := []struct { spanNameFormatter func(string, *http.Request) string expected string }{ {nil, routeTpl}, { func(string, *http.Request) string { return "custom" }, "custom", }, { func(name string, r *http.Request) string { return fmt.Sprintf("%s %s", r.Method, name) }, "GET " + routeTpl, }, } for i, d := range testdata { t.Run(fmt.Sprintf("%d_%s", i, d.expected), func(t *testing.T) { router := mux.NewRouter() router.Use(otelmux.Middleware( "foobar", otelmux.WithTracerProvider(tp), otelmux.WithSpanNameFormatter(d.spanNameFormatter), )) router.HandleFunc(routeTpl, func(w http.ResponseWriter, r *http.Request) {}) r := httptest.NewRequest("GET", "/user/123", nil) w := httptest.NewRecorder() router.ServeHTTP(w, r) spans := exporter.GetSpans() require.Len(t, spans, 1) assert.Equal(t, d.expected, spans[0].Name) exporter.Reset() }) } } func ok(w http.ResponseWriter, _ *http.Request) {} func notfound(w http.ResponseWriter, _ *http.Request) { http.Error(w, "not found", http.StatusNotFound) } func TestSDKIntegration(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider() provider.RegisterSpanProcessor(sr) router := mux.NewRouter() router.Use(otelmux.Middleware("foobar", otelmux.WithTracerProvider(provider))) router.HandleFunc("/user/{id:[0-9]+}", ok) router.HandleFunc("/book/{title}", ok) r0 := httptest.NewRequest("GET", "/user/123", nil) r1 := httptest.NewRequest("GET", "/book/foo", nil) w := httptest.NewRecorder() router.ServeHTTP(w, r0) router.ServeHTTP(w, r1) require.Len(t, sr.Ended(), 2) assertSpan(t, sr.Ended()[0], "/user/{id:[0-9]+}", trace.SpanKindServer, attribute.String("net.host.name", "foobar"), attribute.Int("http.status_code", http.StatusOK), attribute.String("http.method", "GET"), attribute.String("http.route", "/user/{id:[0-9]+}"), ) assertSpan(t, sr.Ended()[1], "/book/{title}", trace.SpanKindServer, attribute.String("net.host.name", "foobar"), attribute.Int("http.status_code", http.StatusOK), attribute.String("http.method", "GET"), attribute.String("http.route", "/book/{title}"), ) } func TestNotFoundIsNotError(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider() provider.RegisterSpanProcessor(sr) router := mux.NewRouter() router.Use(otelmux.Middleware("foobar", otelmux.WithTracerProvider(provider))) router.HandleFunc("/does/not/exist", notfound) r0 := httptest.NewRequest("GET", "/does/not/exist", nil) w := httptest.NewRecorder() router.ServeHTTP(w, r0) require.Len(t, sr.Ended(), 1) assertSpan(t, sr.Ended()[0], "/does/not/exist", trace.SpanKindServer, attribute.String("net.host.name", "foobar"), attribute.Int("http.status_code", http.StatusNotFound), attribute.String("http.method", "GET"), attribute.String("http.route", "/does/not/exist"), ) assert.Equal(t, codes.Unset, sr.Ended()[0].Status().Code) } func assertSpan(t *testing.T, span sdktrace.ReadOnlySpan, name string, kind trace.SpanKind, attrs ...attribute.KeyValue) { assert.Equal(t, name, span.Name()) assert.Equal(t, kind, span.SpanKind()) got := make(map[attribute.Key]attribute.Value, len(span.Attributes())) for _, a := range span.Attributes() { got[a.Key] = a.Value } for _, want := range attrs { if !assert.Contains(t, got, want.Key) { continue } assert.Equal(t, want.Value, got[want.Key]) } } func TestWithPublicEndpoint(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider() provider.RegisterSpanProcessor(sr) remoteSpan := trace.SpanContextConfig{ TraceID: trace.TraceID{0x01}, SpanID: trace.SpanID{0x01}, Remote: true, } prop := propagation.TraceContext{} router := mux.NewRouter() router.Use(otelmux.Middleware("foobar", otelmux.WithPublicEndpoint(), otelmux.WithPropagators(prop), otelmux.WithTracerProvider(provider), )) router.HandleFunc("/with/public/endpoint", func(w http.ResponseWriter, r *http.Request) { s := trace.SpanFromContext(r.Context()) sc := s.SpanContext() // Should be with new root trace. assert.True(t, sc.IsValid()) assert.False(t, sc.IsRemote()) assert.NotEqual(t, remoteSpan.TraceID, sc.TraceID()) }) r0 := httptest.NewRequest("GET", "/with/public/endpoint", nil) w := httptest.NewRecorder() sc := trace.NewSpanContext(remoteSpan) ctx := trace.ContextWithSpanContext(context.Background(), sc) prop.Inject(ctx, propagation.HeaderCarrier(r0.Header)) router.ServeHTTP(w, r0) assert.Equal(t, http.StatusOK, w.Result().StatusCode) //nolint:bodyclose // False positive for httptest.ResponseRecorder: https://github.com/timakin/bodyclose/issues/59. // Recorded span should be linked with an incoming span context. assert.NoError(t, sr.ForceFlush(ctx)) done := sr.Ended() require.Len(t, done, 1) require.Len(t, done[0].Links(), 1, "should contain link") require.True(t, sc.Equal(done[0].Links()[0].SpanContext), "should link incoming span context") } func TestWithPublicEndpointFn(t *testing.T) { remoteSpan := trace.SpanContextConfig{ TraceID: trace.TraceID{0x01}, SpanID: trace.SpanID{0x01}, TraceFlags: trace.FlagsSampled, Remote: true, } prop := propagation.TraceContext{} testdata := []struct { name string fn func(*http.Request) bool handlerAssert func(*testing.T, trace.SpanContext) spansAssert func(*testing.T, trace.SpanContext, []sdktrace.ReadOnlySpan) }{ { name: "with the method returning true", fn: func(r *http.Request) bool { return true }, handlerAssert: func(t *testing.T, sc trace.SpanContext) { // Should be with new root trace. assert.True(t, sc.IsValid()) assert.False(t, sc.IsRemote()) assert.NotEqual(t, remoteSpan.TraceID, sc.TraceID()) }, spansAssert: func(t *testing.T, sc trace.SpanContext, spans []sdktrace.ReadOnlySpan) { require.Len(t, spans, 1) require.Len(t, spans[0].Links(), 1, "should contain link") require.True(t, sc.Equal(spans[0].Links()[0].SpanContext), "should link incoming span context") }, }, { name: "with the method returning false", fn: func(r *http.Request) bool { return false }, handlerAssert: func(t *testing.T, sc trace.SpanContext) { // Should have remote span as parent assert.True(t, sc.IsValid()) assert.False(t, sc.IsRemote()) assert.Equal(t, remoteSpan.TraceID, sc.TraceID()) }, spansAssert: func(t *testing.T, _ trace.SpanContext, spans []sdktrace.ReadOnlySpan) { require.Len(t, spans, 1) require.Empty(t, spans[0].Links(), "should not contain link") }, }, } for _, tt := range testdata { t.Run(tt.name, func(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider() provider.RegisterSpanProcessor(sr) router := mux.NewRouter() router.Use(otelmux.Middleware("foobar", otelmux.WithPublicEndpointFn(tt.fn), otelmux.WithPropagators(prop), otelmux.WithTracerProvider(provider), )) router.HandleFunc("/with/public/endpointfn", func(w http.ResponseWriter, r *http.Request) { s := trace.SpanFromContext(r.Context()) tt.handlerAssert(t, s.SpanContext()) }) r0 := httptest.NewRequest("GET", "/with/public/endpointfn", nil) w := httptest.NewRecorder() sc := trace.NewSpanContext(remoteSpan) ctx := trace.ContextWithSpanContext(context.Background(), sc) prop.Inject(ctx, propagation.HeaderCarrier(r0.Header)) router.ServeHTTP(w, r0) assert.Equal(t, http.StatusOK, w.Result().StatusCode) //nolint:bodyclose // False positive for httptest.ResponseRecorder: https://github.com/timakin/bodyclose/issues/59. // Recorded span should be linked with an incoming span context. assert.NoError(t, sr.ForceFlush(ctx)) spans := sr.Ended() tt.spansAssert(t, sc, spans) }) } } version.go000066400000000000000000000010551470323427300364270ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gorilla/mux/otelmux/test// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package test // import "go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux/test" // Version is the current release version of the gorilla/mux instrumentation test module. func Version() string { return "0.56.0" // This string is updated by the pre_release.sh script during release } // SemVersion is the semantic version to be supplied to tracer/meter creation. // // Deprecated: Use [Version] instead. func SemVersion() string { return Version() } version.go000066400000000000000000000010371470323427300354500ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/gorilla/mux/otelmux// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelmux // import "go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux" // Version is the current release version of the gorilla/mux instrumentation. func Version() string { return "0.56.0" // This string is updated by the pre_release.sh script during release } // SemVersion is the semantic version to be supplied to tracer/meter creation. // // Deprecated: Use [Version] instead. func SemVersion() string { return Version() } open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/labstack/000077500000000000000000000000001470323427300313375ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/labstack/echo/000077500000000000000000000000001470323427300322555ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/labstack/echo/otelecho/000077500000000000000000000000001470323427300340575ustar00rootroot00000000000000config.go000066400000000000000000000030221470323427300355710ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/labstack/echo/otelecho// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelecho // import "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho" import ( "github.com/labstack/echo/v4/middleware" "go.opentelemetry.io/otel/propagation" oteltrace "go.opentelemetry.io/otel/trace" ) // config is used to configure the mux middleware. type config struct { TracerProvider oteltrace.TracerProvider Propagators propagation.TextMapPropagator Skipper middleware.Skipper } // Option specifies instrumentation configuration options. type Option interface { apply(*config) } type optionFunc func(*config) func (o optionFunc) apply(c *config) { o(c) } // WithPropagators specifies propagators to use for extracting // information from the HTTP requests. If none are specified, global // ones will be used. func WithPropagators(propagators propagation.TextMapPropagator) Option { return optionFunc(func(cfg *config) { if propagators != nil { cfg.Propagators = propagators } }) } // WithTracerProvider specifies a tracer provider to use for creating a tracer. // If none is specified, the global provider is used. func WithTracerProvider(provider oteltrace.TracerProvider) Option { return optionFunc(func(cfg *config) { if provider != nil { cfg.TracerProvider = provider } }) } // WithSkipper specifies a skipper for allowing requests to skip generating spans. func WithSkipper(skipper middleware.Skipper) Option { return optionFunc(func(cfg *config) { cfg.Skipper = skipper }) } doc.go000066400000000000000000000006211470323427300350730ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/labstack/echo/otelecho// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package otelecho instruments the labstack/echo package // (https://github.com/labstack/echo). // // Currently only the routing of a received message can be instrumented. To do // so, use the Middleware function. package otelecho // import "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho" echo.go000066400000000000000000000053241470323427300352510ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/labstack/echo/otelecho// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelecho // import "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho" import ( "fmt" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" "go.opentelemetry.io/otel" "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho/internal/semconvutil" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/propagation" semconv "go.opentelemetry.io/otel/semconv/v1.20.0" oteltrace "go.opentelemetry.io/otel/trace" ) const ( tracerKey = "otel-go-contrib-tracer-labstack-echo" // ScopeName is the instrumentation scope name. ScopeName = "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho" ) // Middleware returns echo middleware which will trace incoming requests. func Middleware(service string, opts ...Option) echo.MiddlewareFunc { cfg := config{} for _, opt := range opts { opt.apply(&cfg) } if cfg.TracerProvider == nil { cfg.TracerProvider = otel.GetTracerProvider() } tracer := cfg.TracerProvider.Tracer( ScopeName, oteltrace.WithInstrumentationVersion(Version()), ) if cfg.Propagators == nil { cfg.Propagators = otel.GetTextMapPropagator() } if cfg.Skipper == nil { cfg.Skipper = middleware.DefaultSkipper } return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { if cfg.Skipper(c) { return next(c) } c.Set(tracerKey, tracer) request := c.Request() savedCtx := request.Context() defer func() { request = request.WithContext(savedCtx) c.SetRequest(request) }() ctx := cfg.Propagators.Extract(savedCtx, propagation.HeaderCarrier(request.Header)) opts := []oteltrace.SpanStartOption{ oteltrace.WithAttributes(semconvutil.HTTPServerRequest(service, request)...), oteltrace.WithSpanKind(oteltrace.SpanKindServer), } if path := c.Path(); path != "" { rAttr := semconv.HTTPRoute(path) opts = append(opts, oteltrace.WithAttributes(rAttr)) } spanName := c.Path() if spanName == "" { spanName = fmt.Sprintf("HTTP %s route not found", request.Method) } ctx, span := tracer.Start(ctx, spanName, opts...) defer span.End() // pass the span through the request context c.SetRequest(request.WithContext(ctx)) // serve the request to the next middleware err := next(c) if err != nil { span.SetAttributes(attribute.String("echo.error", err.Error())) // invokes the registered HTTP error handler c.Error(err) } status := c.Response().Status span.SetStatus(semconvutil.HTTPServerStatus(status)) if status > 0 { span.SetAttributes(semconv.HTTPStatusCode(status)) } return err } } } echo_test.go000066400000000000000000000103041470323427300363020ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/labstack/echo/otelecho// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Based on https://github.com/DataDog/dd-trace-go/blob/8fb554ff7cf694267f9077ae35e27ce4689ed8b6/contrib/gin-gonic/gin/gintrace_test.go package otelecho import ( "context" "net/http" "net/http/httptest" "testing" "github.com/labstack/echo/v4" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace/noop" b3prop "go.opentelemetry.io/contrib/propagators/b3" ) func TestGetSpanNotInstrumented(t *testing.T) { router := echo.New() router.GET("/ping", func(c echo.Context) error { // Assert we don't have a span on the context. span := trace.SpanFromContext(c.Request().Context()) ok := !span.SpanContext().IsValid() assert.True(t, ok) return c.String(http.StatusOK, "ok") }) r := httptest.NewRequest("GET", "/ping", nil) w := httptest.NewRecorder() router.ServeHTTP(w, r) response := w.Result() //nolint:bodyclose // False positive for httptest.ResponseRecorder: https://github.com/timakin/bodyclose/issues/59. assert.Equal(t, http.StatusOK, response.StatusCode) } func TestPropagationWithGlobalPropagators(t *testing.T) { provider := noop.NewTracerProvider() otel.SetTextMapPropagator(propagation.TraceContext{}) r := httptest.NewRequest("GET", "/user/123", nil) w := httptest.NewRecorder() ctx := context.Background() sc := trace.NewSpanContext(trace.SpanContextConfig{ TraceID: trace.TraceID{0x01}, SpanID: trace.SpanID{0x01}, }) ctx = trace.ContextWithRemoteSpanContext(ctx, sc) ctx, _ = provider.Tracer(ScopeName).Start(ctx, "test") otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(r.Header)) router := echo.New() router.Use(Middleware("foobar", WithTracerProvider(provider))) router.GET("/user/:id", func(c echo.Context) error { span := trace.SpanFromContext(c.Request().Context()) assert.Equal(t, sc.TraceID(), span.SpanContext().TraceID()) assert.Equal(t, sc.SpanID(), span.SpanContext().SpanID()) return c.NoContent(http.StatusOK) }) router.ServeHTTP(w, r) otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator()) assert.Equal(t, http.StatusOK, w.Result().StatusCode, "should call the 'user' handler") //nolint:bodyclose // False positive for httptest.ResponseRecorder: https://github.com/timakin/bodyclose/issues/59. } func TestPropagationWithCustomPropagators(t *testing.T) { provider := noop.NewTracerProvider() b3 := b3prop.New() r := httptest.NewRequest("GET", "/user/123", nil) w := httptest.NewRecorder() ctx := context.Background() sc := trace.NewSpanContext(trace.SpanContextConfig{ TraceID: trace.TraceID{0x01}, SpanID: trace.SpanID{0x01}, }) ctx = trace.ContextWithRemoteSpanContext(ctx, sc) ctx, _ = provider.Tracer(ScopeName).Start(ctx, "test") b3.Inject(ctx, propagation.HeaderCarrier(r.Header)) router := echo.New() router.Use(Middleware("foobar", WithTracerProvider(provider), WithPropagators(b3))) router.GET("/user/:id", func(c echo.Context) error { span := trace.SpanFromContext(c.Request().Context()) assert.Equal(t, sc.TraceID(), span.SpanContext().TraceID()) assert.Equal(t, sc.SpanID(), span.SpanContext().SpanID()) return c.NoContent(http.StatusOK) }) router.ServeHTTP(w, r) assert.Equal(t, http.StatusOK, w.Result().StatusCode, "should call the 'user' handler") //nolint:bodyclose // False positive for httptest.ResponseRecorder: https://github.com/timakin/bodyclose/issues/59. } func TestSkipper(t *testing.T) { r := httptest.NewRequest("GET", "/ping", nil) w := httptest.NewRecorder() skipper := func(c echo.Context) bool { return c.Request().RequestURI == "/ping" } router := echo.New() router.Use(Middleware("foobar", WithSkipper(skipper))) router.GET("/ping", func(c echo.Context) error { span := trace.SpanFromContext(c.Request().Context()) assert.False(t, span.SpanContext().HasSpanID()) assert.False(t, span.SpanContext().HasTraceID()) return c.NoContent(http.StatusOK) }) router.ServeHTTP(w, r) assert.Equal(t, http.StatusOK, w.Result().StatusCode, "should call the 'ping' handler") //nolint:bodyclose // False positive for httptest.ResponseRecorder: https://github.com/timakin/bodyclose/issues/59. } example/000077500000000000000000000000001470323427300354335ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/labstack/echo/otelechoDockerfile000066400000000000000000000004071470323427300374260ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/labstack/echo/otelecho/example# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 FROM golang:alpine AS base COPY . /src/ WORKDIR /src/instrumentation/github.com/labstack/echo/otelecho/example FROM base AS echo-server RUN go install ./server.go CMD ["/go/bin/server"] README.md000066400000000000000000000012771470323427300367210ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/labstack/echo/otelecho/example# labstack echo instrumentation example An HTTP server using labstack echo and instrumentation. The server has a `/users/:id` endpoint. The server generates span information to `stdout`. These instructions expect you have [docker-compose](https://docs.docker.com/compose/) installed. Bring up the `echo-server` and `echo-client` services to run the example: ```sh docker-compose up --detach echo-server echo-client ``` The `echo-client` service sends just one HTTP request to `echo-server` and then exits. View the span generated by `echo-server` in the logs: ```sh docker-compose logs echo-server ``` Shut down the services when you are finished with the example: ```sh docker-compose down ``` docker-compose.yml000066400000000000000000000010521470323427300410660ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/labstack/echo/otelecho/example# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 version: "3.7" services: echo-client: image: golang:alpine networks: - example command: - "/bin/sh" - "-c" - "wget http://echo-server:8080/users/123 && cat 123" depends_on: - echo-server echo-server: build: dockerfile: $PWD/Dockerfile context: ../../../../../../ ports: - "8080:80" command: - "/bin/sh" - "-c" - "/go/bin/server" networks: - example networks: example: go.mod000066400000000000000000000024171470323427300365450ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/labstack/echo/otelecho/examplemodule go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho/example go 1.22 replace ( go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho => ../ go.opentelemetry.io/contrib/propagators/b3 => ../../../../../../propagators/b3 ) require ( github.com/labstack/echo/v4 v4.12.0 go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho v0.56.0 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0 go.opentelemetry.io/otel/sdk v1.31.0 go.opentelemetry.io/otel/trace v1.31.0 ) require ( github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/google/uuid v1.6.0 // indirect github.com/labstack/gommon v0.4.2 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect golang.org/x/crypto v0.28.0 // indirect golang.org/x/net v0.30.0 // indirect golang.org/x/sys v0.26.0 // indirect golang.org/x/text v0.19.0 // indirect golang.org/x/time v0.7.0 // indirect ) go.sum000066400000000000000000000110671470323427300365730ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/labstack/echo/otelecho/examplegithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0= github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM= github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0 h1:UGZ1QwZWY67Z6BmckTU+9Rxn04m2bD3gD6Mk0OIOCPk= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0/go.mod h1:fcwWuDuaObkkChiDlhEpSq9+X1C0omv+s5mBtToAQ64= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= server.go000066400000000000000000000033361470323427300372750ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/labstack/echo/otelecho/example// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package main import ( "context" "log" "net/http" "github.com/labstack/echo/v4" "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/propagation" sdktrace "go.opentelemetry.io/otel/sdk/trace" oteltrace "go.opentelemetry.io/otel/trace" ) var tracer = otel.Tracer("echo-server") func main() { tp, err := initTracer() if err != nil { log.Fatal(err) } defer func() { if err := tp.Shutdown(context.Background()); err != nil { log.Printf("Error shutting down tracer provider: %v", err) } }() r := echo.New() r.Use(otelecho.Middleware("my-server")) r.GET("/users/:id", func(c echo.Context) error { id := c.Param("id") name := getUser(c.Request().Context(), id) return c.JSON(http.StatusOK, struct { ID string Name string }{ ID: id, Name: name, }) }) _ = r.Start(":8080") } func initTracer() (*sdktrace.TracerProvider, error) { exporter, err := stdout.New(stdout.WithPrettyPrint()) if err != nil { return nil, err } tp := sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithBatcher(exporter), ) otel.SetTracerProvider(tp) otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) return tp, nil } func getUser(ctx context.Context, id string) string { _, span := tracer.Start(ctx, "getUser", oteltrace.WithAttributes(attribute.String("id", id))) defer span.End() if id == "123" { return "otelecho tester" } return "unknown" } go.mod000066400000000000000000000022401470323427300351040ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/labstack/echo/otelechomodule go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho go 1.22 replace go.opentelemetry.io/contrib/propagators/b3 => ../../../../../propagators/b3 require ( github.com/labstack/echo/v4 v4.12.0 github.com/stretchr/testify v1.9.0 go.opentelemetry.io/contrib/propagators/b3 v1.31.0 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/trace v1.31.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/labstack/gommon v0.4.2 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect golang.org/x/crypto v0.28.0 // indirect golang.org/x/net v0.30.0 // indirect golang.org/x/sys v0.26.0 // indirect golang.org/x/text v0.19.0 // indirect golang.org/x/time v0.7.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) go.sum000066400000000000000000000103211470323427300351300ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/labstack/echo/otelechogithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0= github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM= github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= internal/000077500000000000000000000000001470323427300356145ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/labstack/echo/otelechosemconvutil/000077500000000000000000000000001470323427300401645ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/labstack/echo/otelecho/internalgen.go000066400000000000000000000013641470323427300412700ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/labstack/echo/otelecho/internal/semconvutil// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho/internal/semconvutil" // Generate semconvutil package: //go:generate gotmpl --body=../../../../../../../internal/shared/semconvutil/httpconv_test.go.tmpl "--data={}" --out=httpconv_test.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconvutil/httpconv.go.tmpl "--data={}" --out=httpconv.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconvutil/netconv_test.go.tmpl "--data={}" --out=netconv_test.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconvutil/netconv.go.tmpl "--data={}" --out=netconv.go httpconv.go000066400000000000000000000466211470323427300423710ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/labstack/echo/otelecho/internal/semconvutil// Code created by gotmpl. DO NOT MODIFY. // source: internal/shared/semconvutil/httpconv.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho/internal/semconvutil" import ( "fmt" "net/http" "strings" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" semconv "go.opentelemetry.io/otel/semconv/v1.20.0" ) // HTTPClientResponse returns trace attributes for an HTTP response received by a // client from a server. It will return the following attributes if the related // values are defined in resp: "http.status.code", // "http.response_content_length". // // This does not add all OpenTelemetry required attributes for an HTTP event, // it assumes ClientRequest was used to create the span with a complete set of // attributes. If a complete set of attributes can be generated using the // request contained in resp. For example: // // append(HTTPClientResponse(resp), ClientRequest(resp.Request)...) func HTTPClientResponse(resp *http.Response) []attribute.KeyValue { return hc.ClientResponse(resp) } // HTTPClientRequest returns trace attributes for an HTTP request made by a client. // The following attributes are always returned: "http.url", "http.method", // "net.peer.name". The following attributes are returned if the related values // are defined in req: "net.peer.port", "user_agent.original", // "http.request_content_length". func HTTPClientRequest(req *http.Request) []attribute.KeyValue { return hc.ClientRequest(req) } // HTTPClientRequestMetrics returns metric attributes for an HTTP request made by a client. // The following attributes are always returned: "http.method", "net.peer.name". // The following attributes are returned if the // related values are defined in req: "net.peer.port". func HTTPClientRequestMetrics(req *http.Request) []attribute.KeyValue { return hc.ClientRequestMetrics(req) } // HTTPClientStatus returns a span status code and message for an HTTP status code // value received by a client. func HTTPClientStatus(code int) (codes.Code, string) { return hc.ClientStatus(code) } // HTTPServerRequest returns trace attributes for an HTTP request received by a // server. // // The server must be the primary server name if it is known. For example this // would be the ServerName directive // (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache // server, and the server_name directive // (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an // nginx server. More generically, the primary server name would be the host // header value that matches the default virtual host of an HTTP server. It // should include the host identifier and if a port is used to route to the // server that port identifier should be included as an appropriate port // suffix. // // If the primary server name is not known, server should be an empty string. // The req Host will be used to determine the server instead. // // The following attributes are always returned: "http.method", "http.scheme", // "http.target", "net.host.name". The following attributes are returned if // they related values are defined in req: "net.host.port", "net.sock.peer.addr", // "net.sock.peer.port", "user_agent.original", "http.client_ip". func HTTPServerRequest(server string, req *http.Request) []attribute.KeyValue { return hc.ServerRequest(server, req) } // HTTPServerRequestMetrics returns metric attributes for an HTTP request received by a // server. // // The server must be the primary server name if it is known. For example this // would be the ServerName directive // (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache // server, and the server_name directive // (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an // nginx server. More generically, the primary server name would be the host // header value that matches the default virtual host of an HTTP server. It // should include the host identifier and if a port is used to route to the // server that port identifier should be included as an appropriate port // suffix. // // If the primary server name is not known, server should be an empty string. // The req Host will be used to determine the server instead. // // The following attributes are always returned: "http.method", "http.scheme", // "net.host.name". The following attributes are returned if they related // values are defined in req: "net.host.port". func HTTPServerRequestMetrics(server string, req *http.Request) []attribute.KeyValue { return hc.ServerRequestMetrics(server, req) } // HTTPServerStatus returns a span status code and message for an HTTP status code // value returned by a server. Status codes in the 400-499 range are not // returned as errors. func HTTPServerStatus(code int) (codes.Code, string) { return hc.ServerStatus(code) } // httpConv are the HTTP semantic convention attributes defined for a version // of the OpenTelemetry specification. type httpConv struct { NetConv *netConv HTTPClientIPKey attribute.Key HTTPMethodKey attribute.Key HTTPRequestContentLengthKey attribute.Key HTTPResponseContentLengthKey attribute.Key HTTPRouteKey attribute.Key HTTPSchemeHTTP attribute.KeyValue HTTPSchemeHTTPS attribute.KeyValue HTTPStatusCodeKey attribute.Key HTTPTargetKey attribute.Key HTTPURLKey attribute.Key UserAgentOriginalKey attribute.Key } var hc = &httpConv{ NetConv: nc, HTTPClientIPKey: semconv.HTTPClientIPKey, HTTPMethodKey: semconv.HTTPMethodKey, HTTPRequestContentLengthKey: semconv.HTTPRequestContentLengthKey, HTTPResponseContentLengthKey: semconv.HTTPResponseContentLengthKey, HTTPRouteKey: semconv.HTTPRouteKey, HTTPSchemeHTTP: semconv.HTTPSchemeHTTP, HTTPSchemeHTTPS: semconv.HTTPSchemeHTTPS, HTTPStatusCodeKey: semconv.HTTPStatusCodeKey, HTTPTargetKey: semconv.HTTPTargetKey, HTTPURLKey: semconv.HTTPURLKey, UserAgentOriginalKey: semconv.UserAgentOriginalKey, } // ClientResponse returns attributes for an HTTP response received by a client // from a server. The following attributes are returned if the related values // are defined in resp: "http.status.code", "http.response_content_length". // // This does not add all OpenTelemetry required attributes for an HTTP event, // it assumes ClientRequest was used to create the span with a complete set of // attributes. If a complete set of attributes can be generated using the // request contained in resp. For example: // // append(ClientResponse(resp), ClientRequest(resp.Request)...) func (c *httpConv) ClientResponse(resp *http.Response) []attribute.KeyValue { /* The following semantic conventions are returned if present: http.status_code int http.response_content_length int */ var n int if resp.StatusCode > 0 { n++ } if resp.ContentLength > 0 { n++ } attrs := make([]attribute.KeyValue, 0, n) if resp.StatusCode > 0 { attrs = append(attrs, c.HTTPStatusCodeKey.Int(resp.StatusCode)) } if resp.ContentLength > 0 { attrs = append(attrs, c.HTTPResponseContentLengthKey.Int(int(resp.ContentLength))) } return attrs } // ClientRequest returns attributes for an HTTP request made by a client. The // following attributes are always returned: "http.url", "http.method", // "net.peer.name". The following attributes are returned if the related values // are defined in req: "net.peer.port", "user_agent.original", // "http.request_content_length", "user_agent.original". func (c *httpConv) ClientRequest(req *http.Request) []attribute.KeyValue { /* The following semantic conventions are returned if present: http.method string user_agent.original string http.url string net.peer.name string net.peer.port int http.request_content_length int */ /* The following semantic conventions are not returned: http.status_code This requires the response. See ClientResponse. http.response_content_length This requires the response. See ClientResponse. net.sock.family This requires the socket used. net.sock.peer.addr This requires the socket used. net.sock.peer.name This requires the socket used. net.sock.peer.port This requires the socket used. http.resend_count This is something outside of a single request. net.protocol.name The value is the Request is ignored, and the go client will always use "http". net.protocol.version The value in the Request is ignored, and the go client will always use 1.1 or 2.0. */ n := 3 // URL, peer name, proto, and method. var h string if req.URL != nil { h = req.URL.Host } peer, p := firstHostPort(h, req.Header.Get("Host")) port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", p) if port > 0 { n++ } useragent := req.UserAgent() if useragent != "" { n++ } if req.ContentLength > 0 { n++ } attrs := make([]attribute.KeyValue, 0, n) attrs = append(attrs, c.method(req.Method)) var u string if req.URL != nil { // Remove any username/password info that may be in the URL. userinfo := req.URL.User req.URL.User = nil u = req.URL.String() // Restore any username/password info that was removed. req.URL.User = userinfo } attrs = append(attrs, c.HTTPURLKey.String(u)) attrs = append(attrs, c.NetConv.PeerName(peer)) if port > 0 { attrs = append(attrs, c.NetConv.PeerPort(port)) } if useragent != "" { attrs = append(attrs, c.UserAgentOriginalKey.String(useragent)) } if l := req.ContentLength; l > 0 { attrs = append(attrs, c.HTTPRequestContentLengthKey.Int64(l)) } return attrs } // ClientRequestMetrics returns metric attributes for an HTTP request made by a client. The // following attributes are always returned: "http.method", "net.peer.name". // The following attributes are returned if the related values // are defined in req: "net.peer.port". func (c *httpConv) ClientRequestMetrics(req *http.Request) []attribute.KeyValue { /* The following semantic conventions are returned if present: http.method string net.peer.name string net.peer.port int */ n := 2 // method, peer name. var h string if req.URL != nil { h = req.URL.Host } peer, p := firstHostPort(h, req.Header.Get("Host")) port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", p) if port > 0 { n++ } attrs := make([]attribute.KeyValue, 0, n) attrs = append(attrs, c.method(req.Method), c.NetConv.PeerName(peer)) if port > 0 { attrs = append(attrs, c.NetConv.PeerPort(port)) } return attrs } // ServerRequest returns attributes for an HTTP request received by a server. // // The server must be the primary server name if it is known. For example this // would be the ServerName directive // (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache // server, and the server_name directive // (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an // nginx server. More generically, the primary server name would be the host // header value that matches the default virtual host of an HTTP server. It // should include the host identifier and if a port is used to route to the // server that port identifier should be included as an appropriate port // suffix. // // If the primary server name is not known, server should be an empty string. // The req Host will be used to determine the server instead. // // The following attributes are always returned: "http.method", "http.scheme", // "http.target", "net.host.name". The following attributes are returned if they // related values are defined in req: "net.host.port", "net.sock.peer.addr", // "net.sock.peer.port", "user_agent.original", "http.client_ip", // "net.protocol.name", "net.protocol.version". func (c *httpConv) ServerRequest(server string, req *http.Request) []attribute.KeyValue { /* The following semantic conventions are returned if present: http.method string http.scheme string net.host.name string net.host.port int net.sock.peer.addr string net.sock.peer.port int user_agent.original string http.client_ip string net.protocol.name string Note: not set if the value is "http". net.protocol.version string http.target string Note: doesn't include the query parameter. */ /* The following semantic conventions are not returned: http.status_code This requires the response. http.request_content_length This requires the len() of body, which can mutate it. http.response_content_length This requires the response. http.route This is not available. net.sock.peer.name This would require a DNS lookup. net.sock.host.addr The request doesn't have access to the underlying socket. net.sock.host.port The request doesn't have access to the underlying socket. */ n := 4 // Method, scheme, proto, and host name. var host string var p int if server == "" { host, p = splitHostPort(req.Host) } else { // Prioritize the primary server name. host, p = splitHostPort(server) if p < 0 { _, p = splitHostPort(req.Host) } } hostPort := requiredHTTPPort(req.TLS != nil, p) if hostPort > 0 { n++ } peer, peerPort := splitHostPort(req.RemoteAddr) if peer != "" { n++ if peerPort > 0 { n++ } } useragent := req.UserAgent() if useragent != "" { n++ } clientIP := serverClientIP(req.Header.Get("X-Forwarded-For")) if clientIP != "" { n++ } var target string if req.URL != nil { target = req.URL.Path if target != "" { n++ } } protoName, protoVersion := netProtocol(req.Proto) if protoName != "" && protoName != "http" { n++ } if protoVersion != "" { n++ } attrs := make([]attribute.KeyValue, 0, n) attrs = append(attrs, c.method(req.Method)) attrs = append(attrs, c.scheme(req.TLS != nil)) attrs = append(attrs, c.NetConv.HostName(host)) if hostPort > 0 { attrs = append(attrs, c.NetConv.HostPort(hostPort)) } if peer != "" { // The Go HTTP server sets RemoteAddr to "IP:port", this will not be a // file-path that would be interpreted with a sock family. attrs = append(attrs, c.NetConv.SockPeerAddr(peer)) if peerPort > 0 { attrs = append(attrs, c.NetConv.SockPeerPort(peerPort)) } } if useragent != "" { attrs = append(attrs, c.UserAgentOriginalKey.String(useragent)) } if clientIP != "" { attrs = append(attrs, c.HTTPClientIPKey.String(clientIP)) } if target != "" { attrs = append(attrs, c.HTTPTargetKey.String(target)) } if protoName != "" && protoName != "http" { attrs = append(attrs, c.NetConv.NetProtocolName.String(protoName)) } if protoVersion != "" { attrs = append(attrs, c.NetConv.NetProtocolVersion.String(protoVersion)) } return attrs } // ServerRequestMetrics returns metric attributes for an HTTP request received // by a server. // // The server must be the primary server name if it is known. For example this // would be the ServerName directive // (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache // server, and the server_name directive // (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an // nginx server. More generically, the primary server name would be the host // header value that matches the default virtual host of an HTTP server. It // should include the host identifier and if a port is used to route to the // server that port identifier should be included as an appropriate port // suffix. // // If the primary server name is not known, server should be an empty string. // The req Host will be used to determine the server instead. // // The following attributes are always returned: "http.method", "http.scheme", // "net.host.name". The following attributes are returned if they related // values are defined in req: "net.host.port". func (c *httpConv) ServerRequestMetrics(server string, req *http.Request) []attribute.KeyValue { /* The following semantic conventions are returned if present: http.scheme string http.route string http.method string http.status_code int net.host.name string net.host.port int net.protocol.name string Note: not set if the value is "http". net.protocol.version string */ n := 3 // Method, scheme, and host name. var host string var p int if server == "" { host, p = splitHostPort(req.Host) } else { // Prioritize the primary server name. host, p = splitHostPort(server) if p < 0 { _, p = splitHostPort(req.Host) } } hostPort := requiredHTTPPort(req.TLS != nil, p) if hostPort > 0 { n++ } protoName, protoVersion := netProtocol(req.Proto) if protoName != "" { n++ } if protoVersion != "" { n++ } attrs := make([]attribute.KeyValue, 0, n) attrs = append(attrs, c.methodMetric(req.Method)) attrs = append(attrs, c.scheme(req.TLS != nil)) attrs = append(attrs, c.NetConv.HostName(host)) if hostPort > 0 { attrs = append(attrs, c.NetConv.HostPort(hostPort)) } if protoName != "" { attrs = append(attrs, c.NetConv.NetProtocolName.String(protoName)) } if protoVersion != "" { attrs = append(attrs, c.NetConv.NetProtocolVersion.String(protoVersion)) } return attrs } func (c *httpConv) method(method string) attribute.KeyValue { if method == "" { return c.HTTPMethodKey.String(http.MethodGet) } return c.HTTPMethodKey.String(method) } func (c *httpConv) methodMetric(method string) attribute.KeyValue { method = strings.ToUpper(method) switch method { case http.MethodConnect, http.MethodDelete, http.MethodGet, http.MethodHead, http.MethodOptions, http.MethodPatch, http.MethodPost, http.MethodPut, http.MethodTrace: default: method = "_OTHER" } return c.HTTPMethodKey.String(method) } func (c *httpConv) scheme(https bool) attribute.KeyValue { // nolint:revive if https { return c.HTTPSchemeHTTPS } return c.HTTPSchemeHTTP } func serverClientIP(xForwardedFor string) string { if idx := strings.Index(xForwardedFor, ","); idx >= 0 { xForwardedFor = xForwardedFor[:idx] } return xForwardedFor } func requiredHTTPPort(https bool, port int) int { // nolint:revive if https { if port > 0 && port != 443 { return port } } else { if port > 0 && port != 80 { return port } } return -1 } // Return the request host and port from the first non-empty source. func firstHostPort(source ...string) (host string, port int) { for _, hostport := range source { host, port = splitHostPort(hostport) if host != "" || port > 0 { break } } return } // ClientStatus returns a span status code and message for an HTTP status code // value received by a client. func (c *httpConv) ClientStatus(code int) (codes.Code, string) { if code < 100 || code >= 600 { return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) } if code >= 400 { return codes.Error, "" } return codes.Unset, "" } // ServerStatus returns a span status code and message for an HTTP status code // value returned by a server. Status codes in the 400-499 range are not // returned as errors. func (c *httpConv) ServerStatus(code int) (codes.Code, string) { if code < 100 || code >= 600 { return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) } if code >= 500 { return codes.Error, "" } return codes.Unset, "" } httpconv_test.go000066400000000000000000000372171470323427300434310ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/labstack/echo/otelecho/internal/semconvutil// Code created by gotmpl. DO NOT MODIFY. // source: internal/shared/semconvutil/httpconv_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconvutil import ( "net/http" "net/http/httptest" "net/url" "strconv" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" ) func TestHTTPClientResponse(t *testing.T) { const stat, n = 201, 397 resp := &http.Response{ StatusCode: stat, ContentLength: n, } got := HTTPClientResponse(resp) assert.Equal(t, 2, cap(got), "slice capacity") assert.ElementsMatch(t, []attribute.KeyValue{ attribute.Key("http.status_code").Int(stat), attribute.Key("http.response_content_length").Int(n), }, got) } func TestHTTPSClientRequest(t *testing.T) { req := &http.Request{ Method: http.MethodGet, URL: &url.URL{ Scheme: "https", Host: "127.0.0.1:443", Path: "/resource", }, Proto: "HTTP/1.0", ProtoMajor: 1, ProtoMinor: 0, } assert.ElementsMatch( t, []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.url", "https://127.0.0.1:443/resource"), attribute.String("net.peer.name", "127.0.0.1"), }, HTTPClientRequest(req), ) } func TestHTTPSClientRequestMetrics(t *testing.T) { req := &http.Request{ Method: http.MethodGet, URL: &url.URL{ Scheme: "https", Host: "127.0.0.1:443", Path: "/resource", }, Proto: "HTTP/1.0", ProtoMajor: 1, ProtoMinor: 0, } assert.ElementsMatch( t, []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("net.peer.name", "127.0.0.1"), }, HTTPClientRequestMetrics(req), ) } func TestHTTPClientRequest(t *testing.T) { const ( user = "alice" n = 128 agent = "Go-http-client/1.1" ) req := &http.Request{ Method: http.MethodGet, URL: &url.URL{ Scheme: "http", Host: "127.0.0.1:8080", Path: "/resource", }, Proto: "HTTP/1.0", ProtoMajor: 1, ProtoMinor: 0, Header: http.Header{ "User-Agent": []string{agent}, }, ContentLength: n, } req.SetBasicAuth(user, "pswrd") assert.ElementsMatch( t, []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.url", "http://127.0.0.1:8080/resource"), attribute.String("net.peer.name", "127.0.0.1"), attribute.Int("net.peer.port", 8080), attribute.String("user_agent.original", agent), attribute.Int("http.request_content_length", n), }, HTTPClientRequest(req), ) } func TestHTTPClientRequestMetrics(t *testing.T) { const ( user = "alice" n = 128 agent = "Go-http-client/1.1" ) req := &http.Request{ Method: http.MethodGet, URL: &url.URL{ Scheme: "http", Host: "127.0.0.1:8080", Path: "/resource", }, Proto: "HTTP/1.0", ProtoMajor: 1, ProtoMinor: 0, Header: http.Header{ "User-Agent": []string{agent}, }, ContentLength: n, } req.SetBasicAuth(user, "pswrd") assert.ElementsMatch( t, []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("net.peer.name", "127.0.0.1"), attribute.Int("net.peer.port", 8080), }, HTTPClientRequestMetrics(req), ) } func TestHTTPClientRequestRequired(t *testing.T) { req := new(http.Request) var got []attribute.KeyValue assert.NotPanics(t, func() { got = HTTPClientRequest(req) }) want := []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.url", ""), attribute.String("net.peer.name", ""), } assert.Equal(t, want, got) } func TestHTTPServerRequest(t *testing.T) { got := make(chan *http.Request, 1) handler := func(w http.ResponseWriter, r *http.Request) { got <- r w.WriteHeader(http.StatusOK) } srv := httptest.NewServer(http.HandlerFunc(handler)) defer srv.Close() srvURL, err := url.Parse(srv.URL) require.NoError(t, err) srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) require.NoError(t, err) resp, err := srv.Client().Get(srv.URL) require.NoError(t, err) require.NoError(t, resp.Body.Close()) req := <-got peer, peerPort := splitHostPort(req.RemoteAddr) const user = "alice" req.SetBasicAuth(user, "pswrd") const clientIP = "127.0.0.5" req.Header.Add("X-Forwarded-For", clientIP) assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.scheme", "http"), attribute.String("net.host.name", srvURL.Hostname()), attribute.Int("net.host.port", int(srvPort)), attribute.String("net.sock.peer.addr", peer), attribute.Int("net.sock.peer.port", peerPort), attribute.String("user_agent.original", "Go-http-client/1.1"), attribute.String("http.client_ip", clientIP), attribute.String("net.protocol.version", "1.1"), attribute.String("http.target", "/"), }, HTTPServerRequest("", req)) } func TestHTTPServerRequestMetrics(t *testing.T) { got := make(chan *http.Request, 1) handler := func(w http.ResponseWriter, r *http.Request) { got <- r w.WriteHeader(http.StatusOK) } srv := httptest.NewServer(http.HandlerFunc(handler)) defer srv.Close() srvURL, err := url.Parse(srv.URL) require.NoError(t, err) srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) require.NoError(t, err) resp, err := srv.Client().Get(srv.URL) require.NoError(t, err) require.NoError(t, resp.Body.Close()) req := <-got assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.scheme", "http"), attribute.String("net.host.name", srvURL.Hostname()), attribute.Int("net.host.port", int(srvPort)), attribute.String("net.protocol.name", "http"), attribute.String("net.protocol.version", "1.1"), }, HTTPServerRequestMetrics("", req)) } func TestHTTPServerName(t *testing.T) { req := new(http.Request) var got []attribute.KeyValue const ( host = "test.semconv.server" port = 8080 ) portStr := strconv.Itoa(port) server := host + ":" + portStr assert.NotPanics(t, func() { got = HTTPServerRequest(server, req) }) assert.Contains(t, got, attribute.String("net.host.name", host)) assert.Contains(t, got, attribute.Int("net.host.port", port)) req = &http.Request{Host: "alt.host.name:" + portStr} // The server parameter does not include a port, ServerRequest should use // the port in the request Host field. assert.NotPanics(t, func() { got = HTTPServerRequest(host, req) }) assert.Contains(t, got, attribute.String("net.host.name", host)) assert.Contains(t, got, attribute.Int("net.host.port", port)) } func TestHTTPServerRequestFailsGracefully(t *testing.T) { req := new(http.Request) var got []attribute.KeyValue assert.NotPanics(t, func() { got = HTTPServerRequest("", req) }) want := []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.scheme", "http"), attribute.String("net.host.name", ""), } assert.ElementsMatch(t, want, got) } func TestHTTPMethod(t *testing.T) { assert.Equal(t, attribute.String("http.method", "POST"), hc.method("POST")) assert.Equal(t, attribute.String("http.method", "GET"), hc.method("")) assert.Equal(t, attribute.String("http.method", "garbage"), hc.method("garbage")) } func TestHTTPScheme(t *testing.T) { assert.Equal(t, attribute.String("http.scheme", "http"), hc.scheme(false)) assert.Equal(t, attribute.String("http.scheme", "https"), hc.scheme(true)) } func TestHTTPServerClientIP(t *testing.T) { tests := []struct { xForwardedFor string want string }{ {"", ""}, {"127.0.0.1", "127.0.0.1"}, {"127.0.0.1,127.0.0.5", "127.0.0.1"}, } for _, test := range tests { got := serverClientIP(test.xForwardedFor) assert.Equal(t, test.want, got, test.xForwardedFor) } } func TestRequiredHTTPPort(t *testing.T) { tests := []struct { https bool port int want int }{ {true, 443, -1}, {true, 80, 80}, {true, 8081, 8081}, {false, 443, 443}, {false, 80, -1}, {false, 8080, 8080}, } for _, test := range tests { got := requiredHTTPPort(test.https, test.port) assert.Equal(t, test.want, got, test.https, test.port) } } func TestFirstHostPort(t *testing.T) { host, port := "127.0.0.1", 8080 hostport := "127.0.0.1:8080" sources := [][]string{ {hostport}, {"", hostport}, {"", "", hostport}, {"", "", hostport, ""}, {"", "", hostport, "127.0.0.3:80"}, } for _, src := range sources { h, p := firstHostPort(src...) assert.Equal(t, host, h, src) assert.Equal(t, port, p, src) } } func TestHTTPClientStatus(t *testing.T) { tests := []struct { code int stat codes.Code msg bool }{ {0, codes.Error, true}, {http.StatusContinue, codes.Unset, false}, {http.StatusSwitchingProtocols, codes.Unset, false}, {http.StatusProcessing, codes.Unset, false}, {http.StatusEarlyHints, codes.Unset, false}, {http.StatusOK, codes.Unset, false}, {http.StatusCreated, codes.Unset, false}, {http.StatusAccepted, codes.Unset, false}, {http.StatusNonAuthoritativeInfo, codes.Unset, false}, {http.StatusNoContent, codes.Unset, false}, {http.StatusResetContent, codes.Unset, false}, {http.StatusPartialContent, codes.Unset, false}, {http.StatusMultiStatus, codes.Unset, false}, {http.StatusAlreadyReported, codes.Unset, false}, {http.StatusIMUsed, codes.Unset, false}, {http.StatusMultipleChoices, codes.Unset, false}, {http.StatusMovedPermanently, codes.Unset, false}, {http.StatusFound, codes.Unset, false}, {http.StatusSeeOther, codes.Unset, false}, {http.StatusNotModified, codes.Unset, false}, {http.StatusUseProxy, codes.Unset, false}, {306, codes.Unset, false}, {http.StatusTemporaryRedirect, codes.Unset, false}, {http.StatusPermanentRedirect, codes.Unset, false}, {http.StatusBadRequest, codes.Error, false}, {http.StatusUnauthorized, codes.Error, false}, {http.StatusPaymentRequired, codes.Error, false}, {http.StatusForbidden, codes.Error, false}, {http.StatusNotFound, codes.Error, false}, {http.StatusMethodNotAllowed, codes.Error, false}, {http.StatusNotAcceptable, codes.Error, false}, {http.StatusProxyAuthRequired, codes.Error, false}, {http.StatusRequestTimeout, codes.Error, false}, {http.StatusConflict, codes.Error, false}, {http.StatusGone, codes.Error, false}, {http.StatusLengthRequired, codes.Error, false}, {http.StatusPreconditionFailed, codes.Error, false}, {http.StatusRequestEntityTooLarge, codes.Error, false}, {http.StatusRequestURITooLong, codes.Error, false}, {http.StatusUnsupportedMediaType, codes.Error, false}, {http.StatusRequestedRangeNotSatisfiable, codes.Error, false}, {http.StatusExpectationFailed, codes.Error, false}, {http.StatusTeapot, codes.Error, false}, {http.StatusMisdirectedRequest, codes.Error, false}, {http.StatusUnprocessableEntity, codes.Error, false}, {http.StatusLocked, codes.Error, false}, {http.StatusFailedDependency, codes.Error, false}, {http.StatusTooEarly, codes.Error, false}, {http.StatusUpgradeRequired, codes.Error, false}, {http.StatusPreconditionRequired, codes.Error, false}, {http.StatusTooManyRequests, codes.Error, false}, {http.StatusRequestHeaderFieldsTooLarge, codes.Error, false}, {http.StatusUnavailableForLegalReasons, codes.Error, false}, {499, codes.Error, false}, {http.StatusInternalServerError, codes.Error, false}, {http.StatusNotImplemented, codes.Error, false}, {http.StatusBadGateway, codes.Error, false}, {http.StatusServiceUnavailable, codes.Error, false}, {http.StatusGatewayTimeout, codes.Error, false}, {http.StatusHTTPVersionNotSupported, codes.Error, false}, {http.StatusVariantAlsoNegotiates, codes.Error, false}, {http.StatusInsufficientStorage, codes.Error, false}, {http.StatusLoopDetected, codes.Error, false}, {http.StatusNotExtended, codes.Error, false}, {http.StatusNetworkAuthenticationRequired, codes.Error, false}, {600, codes.Error, true}, } for _, test := range tests { t.Run(strconv.Itoa(test.code), func(t *testing.T) { c, msg := HTTPClientStatus(test.code) assert.Equal(t, test.stat, c) if test.msg && msg == "" { t.Errorf("expected non-empty message for %d", test.code) } else if !test.msg && msg != "" { t.Errorf("expected empty message for %d, got: %s", test.code, msg) } }) } } func TestHTTPServerStatus(t *testing.T) { tests := []struct { code int stat codes.Code msg bool }{ {0, codes.Error, true}, {http.StatusContinue, codes.Unset, false}, {http.StatusSwitchingProtocols, codes.Unset, false}, {http.StatusProcessing, codes.Unset, false}, {http.StatusEarlyHints, codes.Unset, false}, {http.StatusOK, codes.Unset, false}, {http.StatusCreated, codes.Unset, false}, {http.StatusAccepted, codes.Unset, false}, {http.StatusNonAuthoritativeInfo, codes.Unset, false}, {http.StatusNoContent, codes.Unset, false}, {http.StatusResetContent, codes.Unset, false}, {http.StatusPartialContent, codes.Unset, false}, {http.StatusMultiStatus, codes.Unset, false}, {http.StatusAlreadyReported, codes.Unset, false}, {http.StatusIMUsed, codes.Unset, false}, {http.StatusMultipleChoices, codes.Unset, false}, {http.StatusMovedPermanently, codes.Unset, false}, {http.StatusFound, codes.Unset, false}, {http.StatusSeeOther, codes.Unset, false}, {http.StatusNotModified, codes.Unset, false}, {http.StatusUseProxy, codes.Unset, false}, {306, codes.Unset, false}, {http.StatusTemporaryRedirect, codes.Unset, false}, {http.StatusPermanentRedirect, codes.Unset, false}, {http.StatusBadRequest, codes.Unset, false}, {http.StatusUnauthorized, codes.Unset, false}, {http.StatusPaymentRequired, codes.Unset, false}, {http.StatusForbidden, codes.Unset, false}, {http.StatusNotFound, codes.Unset, false}, {http.StatusMethodNotAllowed, codes.Unset, false}, {http.StatusNotAcceptable, codes.Unset, false}, {http.StatusProxyAuthRequired, codes.Unset, false}, {http.StatusRequestTimeout, codes.Unset, false}, {http.StatusConflict, codes.Unset, false}, {http.StatusGone, codes.Unset, false}, {http.StatusLengthRequired, codes.Unset, false}, {http.StatusPreconditionFailed, codes.Unset, false}, {http.StatusRequestEntityTooLarge, codes.Unset, false}, {http.StatusRequestURITooLong, codes.Unset, false}, {http.StatusUnsupportedMediaType, codes.Unset, false}, {http.StatusRequestedRangeNotSatisfiable, codes.Unset, false}, {http.StatusExpectationFailed, codes.Unset, false}, {http.StatusTeapot, codes.Unset, false}, {http.StatusMisdirectedRequest, codes.Unset, false}, {http.StatusUnprocessableEntity, codes.Unset, false}, {http.StatusLocked, codes.Unset, false}, {http.StatusFailedDependency, codes.Unset, false}, {http.StatusTooEarly, codes.Unset, false}, {http.StatusUpgradeRequired, codes.Unset, false}, {http.StatusPreconditionRequired, codes.Unset, false}, {http.StatusTooManyRequests, codes.Unset, false}, {http.StatusRequestHeaderFieldsTooLarge, codes.Unset, false}, {http.StatusUnavailableForLegalReasons, codes.Unset, false}, {499, codes.Unset, false}, {http.StatusInternalServerError, codes.Error, false}, {http.StatusNotImplemented, codes.Error, false}, {http.StatusBadGateway, codes.Error, false}, {http.StatusServiceUnavailable, codes.Error, false}, {http.StatusGatewayTimeout, codes.Error, false}, {http.StatusHTTPVersionNotSupported, codes.Error, false}, {http.StatusVariantAlsoNegotiates, codes.Error, false}, {http.StatusInsufficientStorage, codes.Error, false}, {http.StatusLoopDetected, codes.Error, false}, {http.StatusNotExtended, codes.Error, false}, {http.StatusNetworkAuthenticationRequired, codes.Error, false}, {600, codes.Error, true}, } for _, test := range tests { c, msg := HTTPServerStatus(test.code) assert.Equal(t, test.stat, c) if test.msg && msg == "" { t.Errorf("expected non-empty message for %d", test.code) } else if !test.msg && msg != "" { t.Errorf("expected empty message for %d, got: %s", test.code, msg) } } } netconv.go000066400000000000000000000122671470323427300421770ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/labstack/echo/otelecho/internal/semconvutil// Code created by gotmpl. DO NOT MODIFY. // source: internal/shared/semconvutil/netconv.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho/internal/semconvutil" import ( "net" "strconv" "strings" "go.opentelemetry.io/otel/attribute" semconv "go.opentelemetry.io/otel/semconv/v1.20.0" ) // NetTransport returns a trace attribute describing the transport protocol of the // passed network. See the net.Dial for information about acceptable network // values. func NetTransport(network string) attribute.KeyValue { return nc.Transport(network) } // netConv are the network semantic convention attributes defined for a version // of the OpenTelemetry specification. type netConv struct { NetHostNameKey attribute.Key NetHostPortKey attribute.Key NetPeerNameKey attribute.Key NetPeerPortKey attribute.Key NetProtocolName attribute.Key NetProtocolVersion attribute.Key NetSockFamilyKey attribute.Key NetSockPeerAddrKey attribute.Key NetSockPeerPortKey attribute.Key NetSockHostAddrKey attribute.Key NetSockHostPortKey attribute.Key NetTransportOther attribute.KeyValue NetTransportTCP attribute.KeyValue NetTransportUDP attribute.KeyValue NetTransportInProc attribute.KeyValue } var nc = &netConv{ NetHostNameKey: semconv.NetHostNameKey, NetHostPortKey: semconv.NetHostPortKey, NetPeerNameKey: semconv.NetPeerNameKey, NetPeerPortKey: semconv.NetPeerPortKey, NetProtocolName: semconv.NetProtocolNameKey, NetProtocolVersion: semconv.NetProtocolVersionKey, NetSockFamilyKey: semconv.NetSockFamilyKey, NetSockPeerAddrKey: semconv.NetSockPeerAddrKey, NetSockPeerPortKey: semconv.NetSockPeerPortKey, NetSockHostAddrKey: semconv.NetSockHostAddrKey, NetSockHostPortKey: semconv.NetSockHostPortKey, NetTransportOther: semconv.NetTransportOther, NetTransportTCP: semconv.NetTransportTCP, NetTransportUDP: semconv.NetTransportUDP, NetTransportInProc: semconv.NetTransportInProc, } func (c *netConv) Transport(network string) attribute.KeyValue { switch network { case "tcp", "tcp4", "tcp6": return c.NetTransportTCP case "udp", "udp4", "udp6": return c.NetTransportUDP case "unix", "unixgram", "unixpacket": return c.NetTransportInProc default: // "ip:*", "ip4:*", and "ip6:*" all are considered other. return c.NetTransportOther } } // Host returns attributes for a network host address. func (c *netConv) Host(address string) []attribute.KeyValue { h, p := splitHostPort(address) var n int if h != "" { n++ if p > 0 { n++ } } if n == 0 { return nil } attrs := make([]attribute.KeyValue, 0, n) attrs = append(attrs, c.HostName(h)) if p > 0 { attrs = append(attrs, c.HostPort(p)) } return attrs } func (c *netConv) HostName(name string) attribute.KeyValue { return c.NetHostNameKey.String(name) } func (c *netConv) HostPort(port int) attribute.KeyValue { return c.NetHostPortKey.Int(port) } func family(network, address string) string { switch network { case "unix", "unixgram", "unixpacket": return "unix" default: if ip := net.ParseIP(address); ip != nil { if ip.To4() == nil { return "inet6" } return "inet" } } return "" } // Peer returns attributes for a network peer address. func (c *netConv) Peer(address string) []attribute.KeyValue { h, p := splitHostPort(address) var n int if h != "" { n++ if p > 0 { n++ } } if n == 0 { return nil } attrs := make([]attribute.KeyValue, 0, n) attrs = append(attrs, c.PeerName(h)) if p > 0 { attrs = append(attrs, c.PeerPort(p)) } return attrs } func (c *netConv) PeerName(name string) attribute.KeyValue { return c.NetPeerNameKey.String(name) } func (c *netConv) PeerPort(port int) attribute.KeyValue { return c.NetPeerPortKey.Int(port) } func (c *netConv) SockPeerAddr(addr string) attribute.KeyValue { return c.NetSockPeerAddrKey.String(addr) } func (c *netConv) SockPeerPort(port int) attribute.KeyValue { return c.NetSockPeerPortKey.Int(port) } // splitHostPort splits a network address hostport of the form "host", // "host%zone", "[host]", "[host%zone], "host:port", "host%zone:port", // "[host]:port", "[host%zone]:port", or ":port" into host or host%zone and // port. // // An empty host is returned if it is not provided or unparsable. A negative // port is returned if it is not provided or unparsable. func splitHostPort(hostport string) (host string, port int) { port = -1 if strings.HasPrefix(hostport, "[") { addrEnd := strings.LastIndex(hostport, "]") if addrEnd < 0 { // Invalid hostport. return } if i := strings.LastIndex(hostport[addrEnd:], ":"); i < 0 { host = hostport[1:addrEnd] return } } else { if i := strings.LastIndex(hostport, ":"); i < 0 { host = hostport return } } host, pStr, err := net.SplitHostPort(hostport) if err != nil { return } p, err := strconv.ParseUint(pStr, 10, 16) if err != nil { return } return host, int(p) // nolint: gosec // Bitsize checked to be 16 above. } func netProtocol(proto string) (name string, version string) { name, version, _ = strings.Cut(proto, "/") name = strings.ToLower(name) return name, version } netconv_test.go000066400000000000000000000130111470323427300432220ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/labstack/echo/otelecho/internal/semconvutil// Code created by gotmpl. DO NOT MODIFY. // source: internal/shared/semconvutil/netconv_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconvutil import ( "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/attribute" ) const ( addr = "127.0.0.1" port = 1834 ) func TestNetTransport(t *testing.T) { transports := map[string]attribute.KeyValue{ "tcp": attribute.String("net.transport", "ip_tcp"), "tcp4": attribute.String("net.transport", "ip_tcp"), "tcp6": attribute.String("net.transport", "ip_tcp"), "udp": attribute.String("net.transport", "ip_udp"), "udp4": attribute.String("net.transport", "ip_udp"), "udp6": attribute.String("net.transport", "ip_udp"), "unix": attribute.String("net.transport", "inproc"), "unixgram": attribute.String("net.transport", "inproc"), "unixpacket": attribute.String("net.transport", "inproc"), "ip:1": attribute.String("net.transport", "other"), "ip:icmp": attribute.String("net.transport", "other"), "ip4:proto": attribute.String("net.transport", "other"), "ip6:proto": attribute.String("net.transport", "other"), } for network, want := range transports { assert.Equal(t, want, NetTransport(network)) } } func TestNetHost(t *testing.T) { testAddrs(t, []addrTest{ {address: "", expected: nil}, {address: "192.0.0.1", expected: []attribute.KeyValue{ nc.HostName("192.0.0.1"), }}, {address: "192.0.0.1:9090", expected: []attribute.KeyValue{ nc.HostName("192.0.0.1"), nc.HostPort(9090), }}, }, nc.Host) } func TestNetHostName(t *testing.T) { expected := attribute.Key("net.host.name").String(addr) assert.Equal(t, expected, nc.HostName(addr)) } func TestNetHostPort(t *testing.T) { expected := attribute.Key("net.host.port").Int(port) assert.Equal(t, expected, nc.HostPort(port)) } func TestNetPeer(t *testing.T) { testAddrs(t, []addrTest{ {address: "", expected: nil}, {address: "example.com", expected: []attribute.KeyValue{ nc.PeerName("example.com"), }}, {address: "/tmp/file", expected: []attribute.KeyValue{ nc.PeerName("/tmp/file"), }}, {address: "192.0.0.1", expected: []attribute.KeyValue{ nc.PeerName("192.0.0.1"), }}, {address: ":9090", expected: nil}, {address: "192.0.0.1:9090", expected: []attribute.KeyValue{ nc.PeerName("192.0.0.1"), nc.PeerPort(9090), }}, }, nc.Peer) } func TestNetPeerName(t *testing.T) { expected := attribute.Key("net.peer.name").String(addr) assert.Equal(t, expected, nc.PeerName(addr)) } func TestNetPeerPort(t *testing.T) { expected := attribute.Key("net.peer.port").Int(port) assert.Equal(t, expected, nc.PeerPort(port)) } func TestNetSockPeerName(t *testing.T) { expected := attribute.Key("net.sock.peer.addr").String(addr) assert.Equal(t, expected, nc.SockPeerAddr(addr)) } func TestNetSockPeerPort(t *testing.T) { expected := attribute.Key("net.sock.peer.port").Int(port) assert.Equal(t, expected, nc.SockPeerPort(port)) } func TestNetFamily(t *testing.T) { tests := []struct { network string address string expect string }{ {"", "", ""}, {"unix", "", "unix"}, {"unix", "gibberish", "unix"}, {"unixgram", "", "unix"}, {"unixgram", "gibberish", "unix"}, {"unixpacket", "gibberish", "unix"}, {"tcp", "123.0.2.8", "inet"}, {"tcp", "gibberish", ""}, {"", "123.0.2.8", "inet"}, {"", "gibberish", ""}, {"tcp", "fe80::1", "inet6"}, {"", "fe80::1", "inet6"}, } for _, test := range tests { got := family(test.network, test.address) assert.Equal(t, test.expect, got, test.network+"/"+test.address) } } func TestSplitHostPort(t *testing.T) { tests := []struct { hostport string host string port int }{ {"", "", -1}, {":8080", "", 8080}, {"127.0.0.1", "127.0.0.1", -1}, {"www.example.com", "www.example.com", -1}, {"127.0.0.1%25en0", "127.0.0.1%25en0", -1}, {"[]", "", -1}, // Ensure this doesn't panic. {"[fe80::1", "", -1}, {"[fe80::1]", "fe80::1", -1}, {"[fe80::1%25en0]", "fe80::1%25en0", -1}, {"[fe80::1]:8080", "fe80::1", 8080}, {"[fe80::1]::", "", -1}, // Too many colons. {"127.0.0.1:", "127.0.0.1", -1}, {"127.0.0.1:port", "127.0.0.1", -1}, {"127.0.0.1:8080", "127.0.0.1", 8080}, {"www.example.com:8080", "www.example.com", 8080}, {"127.0.0.1%25en0:8080", "127.0.0.1%25en0", 8080}, } for _, test := range tests { h, p := splitHostPort(test.hostport) assert.Equal(t, test.host, h, test.hostport) assert.Equal(t, test.port, p, test.hostport) } } type addrTest struct { address string expected []attribute.KeyValue } func testAddrs(t *testing.T, tests []addrTest, f func(string) []attribute.KeyValue) { t.Helper() for _, test := range tests { got := f(test.address) assert.Equal(t, cap(test.expected), cap(got), "slice capacity") assert.ElementsMatch(t, test.expected, got, test.address) } } func TestNetProtocol(t *testing.T) { type testCase struct { name, version string } tests := map[string]testCase{ "HTTP/1.0": {name: "http", version: "1.0"}, "HTTP/1.1": {name: "http", version: "1.1"}, "HTTP/2": {name: "http", version: "2"}, "HTTP/3": {name: "http", version: "3"}, "SPDY": {name: "spdy"}, "SPDY/2": {name: "spdy", version: "2"}, "QUIC": {name: "quic"}, "unknown/proto/2": {name: "unknown", version: "proto/2"}, "other": {name: "other"}, } for proto, want := range tests { name, version := netProtocol(proto) assert.Equal(t, want.name, name) assert.Equal(t, want.version, version) } } test/000077500000000000000000000000001470323427300347575ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/labstack/echo/otelechodoc.go000066400000000000000000000007021470323427300360520ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/labstack/echo/otelecho/test// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package test validates the otelecho instrumentation with the default SDK. // // This package is in a separate module from the instrumentation it tests to // isolate the dependency of the default SDK and not impose this as a transitive // dependency for users. package test // import "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho/test" echo_test.go000066400000000000000000000153221470323427300372660ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/labstack/echo/otelecho/test// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Based on https://github.com/DataDog/dd-trace-go/blob/8fb554ff7cf694267f9077ae35e27ce4689ed8b6/contrib/gin-gonic/gin/gintrace_test.go package test import ( "errors" "net/http" "net/http/httptest" "testing" "github.com/labstack/echo/v4" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/sdk/trace/tracetest" "go.opentelemetry.io/otel/attribute" oteltrace "go.opentelemetry.io/otel/trace" ) func TestChildSpanFromGlobalTracer(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := trace.NewTracerProvider(trace.WithSpanProcessor(sr)) otel.SetTracerProvider(provider) router := echo.New() router.Use(otelecho.Middleware("foobar")) router.GET("/user/:id", func(c echo.Context) error { return c.NoContent(http.StatusOK) }) r := httptest.NewRequest("GET", "/user/123", nil) w := httptest.NewRecorder() router.ServeHTTP(w, r) assert.Equal(t, http.StatusOK, w.Result().StatusCode, "should call the 'user' handler") //nolint:bodyclose // False positive for httptest.ResponseRecorder: https://github.com/timakin/bodyclose/issues/59. assert.Len(t, sr.Ended(), 1) } func TestChildSpanFromCustomTracer(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := trace.NewTracerProvider(trace.WithSpanProcessor(sr)) router := echo.New() router.Use(otelecho.Middleware("foobar", otelecho.WithTracerProvider(provider))) router.GET("/user/:id", func(c echo.Context) error { return c.NoContent(http.StatusOK) }) r := httptest.NewRequest("GET", "/user/123", nil) w := httptest.NewRecorder() router.ServeHTTP(w, r) assert.Equal(t, http.StatusOK, w.Result().StatusCode, "should call the 'user' handler") //nolint:bodyclose // False positive for httptest.ResponseRecorder: https://github.com/timakin/bodyclose/issues/59. assert.Len(t, sr.Ended(), 1) } func TestTrace200(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := trace.NewTracerProvider(trace.WithSpanProcessor(sr)) router := echo.New() router.Use(otelecho.Middleware("foobar", otelecho.WithTracerProvider(provider))) router.GET("/user/:id", func(c echo.Context) error { id := c.Param("id") return c.String(http.StatusOK, id) }) r := httptest.NewRequest("GET", "/user/123", nil) w := httptest.NewRecorder() // do and verify the request router.ServeHTTP(w, r) response := w.Result() //nolint:bodyclose // False positive for httptest.ResponseRecorder: https://github.com/timakin/bodyclose/issues/59. require.Equal(t, http.StatusOK, response.StatusCode) // verify traces look good spans := sr.Ended() require.Len(t, spans, 1) span := spans[0] assert.Equal(t, "/user/:id", span.Name()) assert.Equal(t, oteltrace.SpanKindServer, span.SpanKind()) attrs := span.Attributes() assert.Contains(t, attrs, attribute.String("net.host.name", "foobar")) assert.Contains(t, attrs, attribute.Int("http.status_code", http.StatusOK)) assert.Contains(t, attrs, attribute.String("http.method", "GET")) assert.Contains(t, attrs, attribute.String("http.route", "/user/:id")) } func TestError(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := trace.NewTracerProvider(trace.WithSpanProcessor(sr)) // setup router := echo.New() router.Use(otelecho.Middleware("foobar", otelecho.WithTracerProvider(provider))) wantErr := errors.New("oh no") // configure a handler that returns an error and 5xx status // code router.GET("/server_err", func(c echo.Context) error { return wantErr }) r := httptest.NewRequest("GET", "/server_err", nil) w := httptest.NewRecorder() router.ServeHTTP(w, r) response := w.Result() //nolint:bodyclose // False positive for httptest.ResponseRecorder: https://github.com/timakin/bodyclose/issues/59. assert.Equal(t, http.StatusInternalServerError, response.StatusCode) // verify the errors and status are correct spans := sr.Ended() require.Len(t, spans, 1) span := spans[0] assert.Equal(t, "/server_err", span.Name()) attrs := span.Attributes() assert.Contains(t, attrs, attribute.String("net.host.name", "foobar")) assert.Contains(t, attrs, attribute.Int("http.status_code", http.StatusInternalServerError)) assert.Contains(t, attrs, attribute.String("echo.error", "oh no")) // server errors set the status assert.Equal(t, codes.Error, span.Status().Code) } func TestStatusError(t *testing.T) { for _, tc := range []struct { name string echoError string statusCode int spanCode codes.Code handler func(c echo.Context) error }{ { name: "StandardError", echoError: "oh no", statusCode: http.StatusInternalServerError, spanCode: codes.Error, handler: func(c echo.Context) error { return errors.New("oh no") }, }, { name: "EchoHTTPServerError", echoError: "code=500, message=my error message", statusCode: http.StatusInternalServerError, spanCode: codes.Error, handler: func(c echo.Context) error { return echo.NewHTTPError(http.StatusInternalServerError, "my error message") }, }, { name: "EchoHTTPClientError", echoError: "code=400, message=my error message", statusCode: http.StatusBadRequest, spanCode: codes.Unset, handler: func(c echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, "my error message") }, }, } { t.Run(tc.name, func(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := trace.NewTracerProvider(trace.WithSpanProcessor(sr)) router := echo.New() router.Use(otelecho.Middleware("foobar", otelecho.WithTracerProvider(provider))) router.GET("/err", tc.handler) r := httptest.NewRequest("GET", "/err", nil) w := httptest.NewRecorder() router.ServeHTTP(w, r) spans := sr.Ended() require.Len(t, spans, 1) span := spans[0] assert.Equal(t, "/err", span.Name()) assert.Equal(t, tc.spanCode, span.Status().Code) attrs := span.Attributes() assert.Contains(t, attrs, attribute.String("net.host.name", "foobar")) assert.Contains(t, attrs, attribute.String("http.route", "/err")) assert.Contains(t, attrs, attribute.String("http.method", "GET")) assert.Contains(t, attrs, attribute.Int("http.status_code", tc.statusCode)) assert.Contains(t, attrs, attribute.String("echo.error", tc.echoError)) }) } } func TestErrorNotSwallowedByMiddleware(t *testing.T) { e := echo.New() r := httptest.NewRequest(http.MethodGet, "/err", nil) w := httptest.NewRecorder() c := e.NewContext(r, w) h := otelecho.Middleware("foobar")(echo.HandlerFunc(func(c echo.Context) error { return assert.AnError })) err := h(c) assert.Equal(t, assert.AnError, err) } go.mod000066400000000000000000000026421470323427300360710ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/labstack/echo/otelecho/test// Deprecated: otelecho has no Code Owner. module go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho/test go 1.22 require ( github.com/labstack/echo/v4 v4.12.0 github.com/stretchr/testify v1.9.0 go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho v0.56.0 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/sdk v1.31.0 go.opentelemetry.io/otel/trace v1.31.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/google/uuid v1.6.0 // indirect github.com/labstack/gommon v0.4.2 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect golang.org/x/crypto v0.28.0 // indirect golang.org/x/net v0.30.0 // indirect golang.org/x/sys v0.26.0 // indirect golang.org/x/text v0.19.0 // indirect golang.org/x/time v0.7.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) replace ( go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho => ../ go.opentelemetry.io/contrib/propagators/b3 => ../../../../../../propagators/b3 ) go.sum000066400000000000000000000110451470323427300361130ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/labstack/echo/otelecho/testgithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0= github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM= github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= version.go000066400000000000000000000010511470323427300367700ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/labstack/echo/otelecho/test// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package test // import "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho/test" // Version is the current release version of the echo instrumentation test module. func Version() string { return "0.56.0" // This string is updated by the pre_release.sh script during release } // SemVersion is the semantic version to be supplied to tracer/meter creation. // // Deprecated: Use [Version] instead. func SemVersion() string { return Version() } version.go000066400000000000000000000010341470323427300360120ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/github.com/labstack/echo/otelecho// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelecho // import "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho" // Version is the current release version of the echo instrumentation. func Version() string { return "0.56.0" // This string is updated by the pre_release.sh script during release } // SemVersion is the semantic version to be supplied to tracer/meter creation. // // Deprecated: Use [Version] instead. func SemVersion() string { return Version() } open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/go.mongodb.org/000077500000000000000000000000001470323427300303335ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/go.mongodb.org/mongo-driver/000077500000000000000000000000001470323427300327435ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/go.mongodb.org/mongo-driver/mongo/000077500000000000000000000000001470323427300340625ustar00rootroot00000000000000otelmongo/000077500000000000000000000000001470323427300360065ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/go.mongodb.org/mongo-driver/mongoconfig.go000066400000000000000000000034471470323427300376120ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelmongo // import "go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo" import ( "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/trace" ) // ScopeName is the instrumentation scope name. const ScopeName = "go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo" // config is used to configure the mongo tracer. type config struct { TracerProvider trace.TracerProvider Tracer trace.Tracer CommandAttributeDisabled bool } // newConfig returns a config with all Options set. func newConfig(opts ...Option) config { cfg := config{ TracerProvider: otel.GetTracerProvider(), CommandAttributeDisabled: true, } for _, opt := range opts { opt.apply(&cfg) } cfg.Tracer = cfg.TracerProvider.Tracer( ScopeName, trace.WithInstrumentationVersion(Version()), ) return cfg } // Option specifies instrumentation configuration options. type Option interface { apply(*config) } type optionFunc func(*config) func (o optionFunc) apply(c *config) { o(c) } // WithTracerProvider specifies a tracer provider to use for creating a tracer. // If none is specified, the global provider is used. func WithTracerProvider(provider trace.TracerProvider) Option { return optionFunc(func(cfg *config) { if provider != nil { cfg.TracerProvider = provider } }) } // WithCommandAttributeDisabled specifies if the MongoDB command is added as an attribute to Spans or not. // This is disabled by default and the MongoDB command will not be added as an attribute // to Spans if this option is not provided. func WithCommandAttributeDisabled(disabled bool) Option { return optionFunc(func(cfg *config) { cfg.CommandAttributeDisabled = disabled }) } doc.go000066400000000000000000000012721470323427300371040ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package otelmongo instruments go.mongodb.org/mongo-driver/mongo. // // This package is compatible with v0.2.0 of // go.mongodb.org/mongo-driver/mongo. // // `NewMonitor` will return an event.CommandMonitor which is used to trace // requests. // // This code was originally based on the following: // - https://github.com/DataDog/dd-trace-go/tree/02f0449efa3cb382d499fadc873957385dcb2192/contrib/go.mongodb.org/mongo-driver/mongo // - https://github.com/DataDog/dd-trace-go/tree/v1.23.3/ddtrace/ext package otelmongo // import "go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo" example_test.go000066400000000000000000000017271470323427300410360ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelmongo_test import ( "context" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" "go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo" ) func Example() { // connect to MongoDB opts := options.Client() opts.Monitor = otelmongo.NewMonitor() opts.ApplyURI("mongodb://localhost:27017") client, err := mongo.Connect(context.Background(), opts) if err != nil { panic(err) } db := client.Database("example") inventory := db.Collection("inventory") _, err = inventory.InsertOne(context.Background(), bson.D{ {Key: "item", Value: "canvas"}, {Key: "qty", Value: 100}, {Key: "attributes", Value: bson.A{"cotton"}}, {Key: "size", Value: bson.D{ {Key: "h", Value: 28}, {Key: "w", Value: 35.5}, {Key: "uom", Value: "cm"}, }}, }) if err != nil { panic(err) } } go.mod000066400000000000000000000015321470323427300371150ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongomodule go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo go 1.22 require ( go.mongodb.org/mongo-driver v1.17.1 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/trace v1.31.0 ) require ( github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/klauspost/compress v1.17.11 // indirect github.com/montanaflynn/stats v0.7.1 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/scram v1.1.2 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect golang.org/x/crypto v0.28.0 // indirect golang.org/x/sync v0.8.0 // indirect golang.org/x/text v0.19.0 // indirect ) go.sum000066400000000000000000000137551470323427300371540ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongogithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.mongodb.org/mongo-driver v1.17.1 h1:Wic5cJIwJgSpBhe3lx3+/RybR5PiYRMpVFgO7cOHyIM= go.mongodb.org/mongo-driver v1.17.1/go.mod h1:wwWm/+BuOddhcq3n68LKRmgk2wXzmF6s0SFOa0GINL4= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= mongo.go000066400000000000000000000075231470323427300374630ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelmongo // import "go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo" import ( "context" "fmt" "strconv" "strings" "sync" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" semconv "go.opentelemetry.io/otel/semconv/v1.17.0" "go.opentelemetry.io/otel/trace" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/event" ) type spanKey struct { ConnectionID string RequestID int64 } type monitor struct { sync.Mutex spans map[spanKey]trace.Span cfg config } func (m *monitor) Started(ctx context.Context, evt *event.CommandStartedEvent) { var spanName string hostname, port := peerInfo(evt) attrs := []attribute.KeyValue{ semconv.DBSystemMongoDB, semconv.DBOperation(evt.CommandName), semconv.DBName(evt.DatabaseName), semconv.NetPeerName(hostname), semconv.NetPeerPort(port), semconv.NetTransportTCP, } if !m.cfg.CommandAttributeDisabled { attrs = append(attrs, semconv.DBStatement(sanitizeCommand(evt.Command))) } if collection, err := extractCollection(evt); err == nil && collection != "" { spanName = collection + "." attrs = append(attrs, semconv.DBMongoDBCollection(collection)) } spanName += evt.CommandName opts := []trace.SpanStartOption{ trace.WithSpanKind(trace.SpanKindClient), trace.WithAttributes(attrs...), } _, span := m.cfg.Tracer.Start(ctx, spanName, opts...) key := spanKey{ ConnectionID: evt.ConnectionID, RequestID: evt.RequestID, } m.Lock() m.spans[key] = span m.Unlock() } func (m *monitor) Succeeded(ctx context.Context, evt *event.CommandSucceededEvent) { m.Finished(&evt.CommandFinishedEvent, nil) } func (m *monitor) Failed(ctx context.Context, evt *event.CommandFailedEvent) { m.Finished(&evt.CommandFinishedEvent, fmt.Errorf("%s", evt.Failure)) } func (m *monitor) Finished(evt *event.CommandFinishedEvent, err error) { key := spanKey{ ConnectionID: evt.ConnectionID, RequestID: evt.RequestID, } m.Lock() span, ok := m.spans[key] if ok { delete(m.spans, key) } m.Unlock() if !ok { return } if err != nil { span.SetStatus(codes.Error, err.Error()) } span.End() } // TODO sanitize values where possible, then re-enable `db.statement` span attributes default. // TODO limit maximum size. func sanitizeCommand(command bson.Raw) string { b, _ := bson.MarshalExtJSON(command, false, false) return string(b) } // extractCollection extracts the collection for the given mongodb command event. // For CRUD operations, this is the first key/value string pair in the bson // document where key == "" (e.g. key == "insert"). // For database meta-level operations, such a key may not exist. func extractCollection(evt *event.CommandStartedEvent) (string, error) { elt, err := evt.Command.IndexErr(0) if err != nil { return "", err } if key, err := elt.KeyErr(); err == nil && key == evt.CommandName { var v bson.RawValue if v, err = elt.ValueErr(); err != nil || v.Type != bson.TypeString { return "", err } return v.StringValue(), nil } return "", fmt.Errorf("collection name not found") } // NewMonitor creates a new mongodb event CommandMonitor. func NewMonitor(opts ...Option) *event.CommandMonitor { cfg := newConfig(opts...) m := &monitor{ spans: make(map[spanKey]trace.Span), cfg: cfg, } return &event.CommandMonitor{ Started: m.Started, Succeeded: m.Succeeded, Failed: m.Failed, } } func peerInfo(evt *event.CommandStartedEvent) (hostname string, port int) { hostname = evt.ConnectionID port = 27017 if idx := strings.IndexByte(hostname, '['); idx >= 0 { hostname = hostname[:idx] } if idx := strings.IndexByte(hostname, ':'); idx >= 0 { port = func(p int, e error) int { return p }(strconv.Atoi(hostname[idx+1:])) hostname = hostname[:idx] } return hostname, port } test/000077500000000000000000000000001470323427300367655ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongodoc.go000066400000000000000000000007051470323427300400630ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo/test// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 /* Package test validates the otelmongo instrumentation with the default SDK. This package is in a separate module from the instrumentation it tests to isolate the dependency of the default SDK and not impose this as a transitive dependency for users. */ package test // import "go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo/test" go.mod000066400000000000000000000026001470323427300400710ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo/testmodule go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo/test go 1.22 require ( github.com/stretchr/testify v1.9.0 go.mongodb.org/mongo-driver v1.17.1 go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo v0.56.0 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/sdk v1.31.0 go.opentelemetry.io/otel/trace v1.31.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/uuid v1.6.0 // indirect github.com/klauspost/compress v1.17.11 // indirect github.com/montanaflynn/stats v0.7.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/scram v1.1.2 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect golang.org/x/crypto v0.28.0 // indirect golang.org/x/sync v0.8.0 // indirect golang.org/x/sys v0.26.0 // indirect golang.org/x/text v0.19.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) replace ( go.opentelemetry.io/contrib => ../../../../../.. go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo => ../ ) go.sum000066400000000000000000000152531470323427300401260ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo/testgithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.mongodb.org/mongo-driver v1.17.1 h1:Wic5cJIwJgSpBhe3lx3+/RybR5PiYRMpVFgO7cOHyIM= go.mongodb.org/mongo-driver v1.17.1/go.mod h1:wwWm/+BuOddhcq3n68LKRmgk2wXzmF6s0SFOa0GINL4= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= mongo_test.go000066400000000000000000000177031470323427300415020ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo/test// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package test import ( "context" "testing" "time" "github.com/stretchr/testify/assert" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/integration/mtest" "go.mongodb.org/mongo-driver/mongo/options" "go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/sdk/trace/tracetest" "go.opentelemetry.io/otel/trace" ) type validator func(sdktrace.ReadOnlySpan) bool func TestDBCrudOperation(t *testing.T) { commonValidators := []validator{ func(s sdktrace.ReadOnlySpan) bool { return assert.Equal(t, "test-collection.insert", s.Name(), "expected %s", s.Name()) }, func(s sdktrace.ReadOnlySpan) bool { return assert.Contains(t, s.Attributes(), attribute.String("db.operation", "insert")) }, func(s sdktrace.ReadOnlySpan) bool { return assert.Contains(t, s.Attributes(), attribute.String("db.mongodb.collection", "test-collection")) }, func(s sdktrace.ReadOnlySpan) bool { return assert.Equal(t, codes.Unset, s.Status().Code) }, } tt := []struct { title string operation func(context.Context, *mongo.Database) (interface{}, error) mockResponses []bson.D excludeCommand bool validators []validator }{ { title: "insert", operation: func(ctx context.Context, db *mongo.Database) (interface{}, error) { return db.Collection("test-collection").InsertOne(ctx, bson.D{{Key: "test-item", Value: "test-value"}}) }, mockResponses: []bson.D{{{Key: "ok", Value: 1}}}, excludeCommand: false, validators: append(commonValidators, func(s sdktrace.ReadOnlySpan) bool { for _, attr := range s.Attributes() { if attr.Key == "db.statement" { return assert.Contains(t, attr.Value.AsString(), `"test-item":"test-value"`) } } return false }), }, { title: "insert", operation: func(ctx context.Context, db *mongo.Database) (interface{}, error) { return db.Collection("test-collection").InsertOne(ctx, bson.D{{Key: "test-item", Value: "test-value"}}) }, mockResponses: []bson.D{{{Key: "ok", Value: 1}}}, excludeCommand: true, validators: append(commonValidators, func(s sdktrace.ReadOnlySpan) bool { for _, attr := range s.Attributes() { if attr.Key == "db.statement" { return false } } return true }), }, } for _, tc := range tt { tc := tc title := tc.title if tc.excludeCommand { title = title + "/excludeCommand" } else { title = title + "/includeCommand" } mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock)) mt.Run(title, func(mt *mtest.T) { sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr)) ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) defer cancel() ctx, span := provider.Tracer("test").Start(ctx, "mongodb-test") addr := "mongodb://localhost:27017/?connect=direct" opts := options.Client() opts.Monitor = otelmongo.NewMonitor( otelmongo.WithTracerProvider(provider), otelmongo.WithCommandAttributeDisabled(tc.excludeCommand), ) opts.ApplyURI(addr) mt.ResetClient(opts) mt.AddMockResponses(tc.mockResponses...) _, err := tc.operation(ctx, mt.Client.Database("test-database")) if err != nil { mt.Error(err) } span.End() spans := sr.Ended() if !assert.Len(mt, spans, 2, "expected 2 spans, received %d", len(spans)) { mt.FailNow() } assert.Len(mt, spans, 2) assert.Equal(mt, spans[0].SpanContext().TraceID(), spans[1].SpanContext().TraceID()) assert.Equal(mt, spans[0].Parent().SpanID(), spans[1].SpanContext().SpanID()) assert.Equal(mt, span.SpanContext().SpanID(), spans[1].SpanContext().SpanID()) s := spans[0] assert.Equal(mt, trace.SpanKindClient, s.SpanKind()) attrs := s.Attributes() assert.Contains(mt, attrs, attribute.String("db.system", "mongodb")) assert.Contains(mt, attrs, attribute.String("net.peer.name", "")) assert.Contains(mt, attrs, attribute.Int64("net.peer.port", int64(27017))) assert.Contains(mt, attrs, attribute.String("net.transport", "ip_tcp")) assert.Contains(mt, attrs, attribute.String("db.name", "test-database")) for _, v := range tc.validators { assert.True(mt, v(s)) } }) } } func TestDBCollectionAttribute(t *testing.T) { tt := []struct { title string operation func(context.Context, *mongo.Database) (interface{}, error) mockResponses []bson.D validators []validator }{ { title: "delete", operation: func(ctx context.Context, db *mongo.Database) (interface{}, error) { return db.Collection("test-collection").DeleteOne(ctx, bson.D{{Key: "test-item"}}) }, mockResponses: []bson.D{{{Key: "ok", Value: 1}}}, validators: []validator{ func(s sdktrace.ReadOnlySpan) bool { return assert.Equal(t, "test-collection.delete", s.Name()) }, func(s sdktrace.ReadOnlySpan) bool { return assert.Contains(t, s.Attributes(), attribute.String("db.operation", "delete")) }, func(s sdktrace.ReadOnlySpan) bool { return assert.Contains(t, s.Attributes(), attribute.String("db.mongodb.collection", "test-collection")) }, func(s sdktrace.ReadOnlySpan) bool { return assert.Equal(t, codes.Unset, s.Status().Code) }, }, }, { title: "listCollectionNames", operation: func(ctx context.Context, db *mongo.Database) (interface{}, error) { return db.ListCollectionNames(ctx, bson.D{}) }, mockResponses: []bson.D{ { {Key: "ok", Value: 1}, {Key: "cursor", Value: bson.D{{Key: "firstBatch", Value: bson.A{}}}}, }, }, validators: []validator{ func(s sdktrace.ReadOnlySpan) bool { return assert.Equal(t, "listCollections", s.Name()) }, func(s sdktrace.ReadOnlySpan) bool { return assert.Contains(t, s.Attributes(), attribute.String("db.operation", "listCollections")) }, func(s sdktrace.ReadOnlySpan) bool { return assert.Equal(t, codes.Unset, s.Status().Code) }, }, }, } for _, tc := range tt { tc := tc mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock)) mt.Run(tc.title, func(mt *mtest.T) { sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr)) ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) defer cancel() ctx, span := provider.Tracer("test").Start(ctx, "mongodb-test") addr := "mongodb://localhost:27017/?connect=direct" opts := options.Client() opts.Monitor = otelmongo.NewMonitor( otelmongo.WithTracerProvider(provider), otelmongo.WithCommandAttributeDisabled(true), ) opts.ApplyURI(addr) mt.ResetClient(opts) mt.AddMockResponses(tc.mockResponses...) _, err := tc.operation(ctx, mt.Client.Database("test-database")) if err != nil { mt.Error(err) } span.End() spans := sr.Ended() if !assert.Len(mt, spans, 2, "expected 2 spans, received %d", len(spans)) { mt.FailNow() } assert.Len(mt, spans, 2) assert.Equal(mt, spans[0].SpanContext().TraceID(), spans[1].SpanContext().TraceID()) assert.Equal(mt, spans[0].Parent().SpanID(), spans[1].SpanContext().SpanID()) assert.Equal(mt, span.SpanContext().SpanID(), spans[1].SpanContext().SpanID()) s := spans[0] assert.Equal(mt, trace.SpanKindClient, s.SpanKind()) attrs := s.Attributes() assert.Contains(mt, attrs, attribute.String("db.system", "mongodb")) assert.Contains(mt, attrs, attribute.String("net.peer.name", "")) assert.Contains(mt, attrs, attribute.Int64("net.peer.port", int64(27017))) assert.Contains(mt, attrs, attribute.String("net.transport", "ip_tcp")) assert.Contains(mt, attrs, attribute.String("db.name", "test-database")) for _, v := range tc.validators { assert.True(mt, v(s)) } }) } } version.go000066400000000000000000000010731470323427300410020ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo/test// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package test // import "go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo/test" // Version is the current release version of the mongo-driver instrumentation test module. func Version() string { return "0.56.0" // This string is updated by the pre_release.sh script during release } // SemVersion is the semantic version to be supplied to tracer/meter creation. // // Deprecated: Use [Version] instead. func SemVersion() string { return Version() } version.go000066400000000000000000000010571470323427300400250ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelmongo // import "go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo" // Version is the current release version of the mongo-driver instrumentation. func Version() string { return "0.56.0" // This string is updated by the pre_release.sh script during release } // SemVersion is the semantic version to be supplied to tracer/meter creation. // // Deprecated: Use [Version] instead. func SemVersion() string { return Version() } open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/google.golang.org/000077500000000000000000000000001470323427300310245ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/google.golang.org/grpc/000077500000000000000000000000001470323427300317575ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/google.golang.org/grpc/otelgrpc/000077500000000000000000000000001470323427300335765ustar00rootroot00000000000000benchmark_test.go000066400000000000000000000047661470323427300370540ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/google.golang.org/grpc/otelgrpc// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelgrpc_test import ( "context" "net" "testing" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/test/bufconn" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/internal/test" "go.opentelemetry.io/otel/trace/noop" pb "google.golang.org/grpc/interop/grpc_testing" ) const bufSize = 2048 var tracerProvider = noop.NewTracerProvider() func benchmark(b *testing.B, cOpt []grpc.DialOption, sOpt []grpc.ServerOption) { l := bufconn.Listen(bufSize) defer l.Close() s := grpc.NewServer(sOpt...) pb.RegisterTestServiceServer(s, test.NewTestServer()) go func() { if err := s.Serve(l); err != nil { panic(err) } }() defer s.Stop() ctx := context.Background() dial := func(context.Context, string) (net.Conn, error) { return l.Dial() } conn, err := grpc.NewClient( "passthrough:bufnet", append([]grpc.DialOption{ grpc.WithContextDialer(dial), grpc.WithTransportCredentials(insecure.NewCredentials()), }, cOpt...)..., ) if err != nil { b.Fatalf("Failed to dial bufnet: %v", err) } defer conn.Close() client := pb.NewTestServiceClient(conn) b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { test.DoEmptyUnaryCall(ctx, client) test.DoLargeUnaryCall(ctx, client) test.DoClientStreaming(ctx, client) test.DoServerStreaming(ctx, client) test.DoPingPong(ctx, client) test.DoEmptyStream(ctx, client) } b.StopTimer() } func BenchmarkNoInstrumentation(b *testing.B) { benchmark(b, nil, nil) } func BenchmarkUnaryServerInterceptor(b *testing.B) { benchmark(b, nil, []grpc.ServerOption{ grpc.UnaryInterceptor(otelgrpc.UnaryServerInterceptor( otelgrpc.WithTracerProvider(tracerProvider), )), }) } func BenchmarkStreamServerInterceptor(b *testing.B) { benchmark(b, nil, []grpc.ServerOption{ grpc.StreamInterceptor(otelgrpc.StreamServerInterceptor( otelgrpc.WithTracerProvider(tracerProvider), )), }) } func BenchmarkUnaryClientInterceptor(b *testing.B) { benchmark(b, []grpc.DialOption{ grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor( otelgrpc.WithTracerProvider(tracerProvider), )), }, nil) } func BenchmarkStreamClientInterceptor(b *testing.B) { benchmark(b, []grpc.DialOption{ grpc.WithStreamInterceptor(otelgrpc.StreamClientInterceptor( otelgrpc.WithTracerProvider(tracerProvider), )), }, nil) } config.go000066400000000000000000000175771470323427300353340ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/google.golang.org/grpc/otelgrpc// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelgrpc // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" import ( "google.golang.org/grpc/stats" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/metric/noop" "go.opentelemetry.io/otel/propagation" semconv "go.opentelemetry.io/otel/semconv/v1.17.0" "go.opentelemetry.io/otel/trace" ) const ( // ScopeName is the instrumentation scope name. ScopeName = "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" // GRPCStatusCodeKey is convention for numeric status code of a gRPC request. GRPCStatusCodeKey = attribute.Key("rpc.grpc.status_code") ) // InterceptorFilter is a predicate used to determine whether a given request in // interceptor info should be instrumented. A InterceptorFilter must return true if // the request should be traced. // // Deprecated: Use stats handlers instead. type InterceptorFilter func(*InterceptorInfo) bool // Filter is a predicate used to determine whether a given request in // should be instrumented by the attached RPC tag info. // A Filter must return true if the request should be instrumented. type Filter func(*stats.RPCTagInfo) bool // config is a group of options for this instrumentation. type config struct { Filter Filter InterceptorFilter InterceptorFilter Propagators propagation.TextMapPropagator TracerProvider trace.TracerProvider MeterProvider metric.MeterProvider SpanStartOptions []trace.SpanStartOption SpanAttributes []attribute.KeyValue MetricAttributes []attribute.KeyValue ReceivedEvent bool SentEvent bool tracer trace.Tracer meter metric.Meter rpcDuration metric.Float64Histogram rpcRequestSize metric.Int64Histogram rpcResponseSize metric.Int64Histogram rpcRequestsPerRPC metric.Int64Histogram rpcResponsesPerRPC metric.Int64Histogram } // Option applies an option value for a config. type Option interface { apply(*config) } // newConfig returns a config configured with all the passed Options. func newConfig(opts []Option, role string) *config { c := &config{ Propagators: otel.GetTextMapPropagator(), TracerProvider: otel.GetTracerProvider(), MeterProvider: otel.GetMeterProvider(), } for _, o := range opts { o.apply(c) } c.tracer = c.TracerProvider.Tracer( ScopeName, trace.WithInstrumentationVersion(SemVersion()), ) c.meter = c.MeterProvider.Meter( ScopeName, metric.WithInstrumentationVersion(Version()), metric.WithSchemaURL(semconv.SchemaURL), ) var err error c.rpcDuration, err = c.meter.Float64Histogram("rpc."+role+".duration", metric.WithDescription("Measures the duration of inbound RPC."), metric.WithUnit("ms")) if err != nil { otel.Handle(err) if c.rpcDuration == nil { c.rpcDuration = noop.Float64Histogram{} } } c.rpcRequestSize, err = c.meter.Int64Histogram("rpc."+role+".request.size", metric.WithDescription("Measures size of RPC request messages (uncompressed)."), metric.WithUnit("By")) if err != nil { otel.Handle(err) if c.rpcRequestSize == nil { c.rpcRequestSize = noop.Int64Histogram{} } } c.rpcResponseSize, err = c.meter.Int64Histogram("rpc."+role+".response.size", metric.WithDescription("Measures size of RPC response messages (uncompressed)."), metric.WithUnit("By")) if err != nil { otel.Handle(err) if c.rpcResponseSize == nil { c.rpcResponseSize = noop.Int64Histogram{} } } c.rpcRequestsPerRPC, err = c.meter.Int64Histogram("rpc."+role+".requests_per_rpc", metric.WithDescription("Measures the number of messages received per RPC. Should be 1 for all non-streaming RPCs."), metric.WithUnit("{count}")) if err != nil { otel.Handle(err) if c.rpcRequestsPerRPC == nil { c.rpcRequestsPerRPC = noop.Int64Histogram{} } } c.rpcResponsesPerRPC, err = c.meter.Int64Histogram("rpc."+role+".responses_per_rpc", metric.WithDescription("Measures the number of messages received per RPC. Should be 1 for all non-streaming RPCs."), metric.WithUnit("{count}")) if err != nil { otel.Handle(err) if c.rpcResponsesPerRPC == nil { c.rpcResponsesPerRPC = noop.Int64Histogram{} } } return c } type propagatorsOption struct{ p propagation.TextMapPropagator } func (o propagatorsOption) apply(c *config) { if o.p != nil { c.Propagators = o.p } } // WithPropagators returns an Option to use the Propagators when extracting // and injecting trace context from requests. func WithPropagators(p propagation.TextMapPropagator) Option { return propagatorsOption{p: p} } type tracerProviderOption struct{ tp trace.TracerProvider } func (o tracerProviderOption) apply(c *config) { if o.tp != nil { c.TracerProvider = o.tp } } // WithInterceptorFilter returns an Option to use the request filter. // // Deprecated: Use stats handlers instead. func WithInterceptorFilter(f InterceptorFilter) Option { return interceptorFilterOption{f: f} } type interceptorFilterOption struct { f InterceptorFilter } func (o interceptorFilterOption) apply(c *config) { if o.f != nil { c.InterceptorFilter = o.f } } // WithFilter returns an Option to use the request filter. func WithFilter(f Filter) Option { return filterOption{f: f} } type filterOption struct { f Filter } func (o filterOption) apply(c *config) { if o.f != nil { c.Filter = o.f } } // WithTracerProvider returns an Option to use the TracerProvider when // creating a Tracer. func WithTracerProvider(tp trace.TracerProvider) Option { return tracerProviderOption{tp: tp} } type meterProviderOption struct{ mp metric.MeterProvider } func (o meterProviderOption) apply(c *config) { if o.mp != nil { c.MeterProvider = o.mp } } // WithMeterProvider returns an Option to use the MeterProvider when // creating a Meter. If this option is not provide the global MeterProvider will be used. func WithMeterProvider(mp metric.MeterProvider) Option { return meterProviderOption{mp: mp} } // Event type that can be recorded, see WithMessageEvents. type Event int // Different types of events that can be recorded, see WithMessageEvents. const ( ReceivedEvents Event = iota SentEvents ) type messageEventsProviderOption struct { events []Event } func (m messageEventsProviderOption) apply(c *config) { for _, e := range m.events { switch e { case ReceivedEvents: c.ReceivedEvent = true case SentEvents: c.SentEvent = true } } } // WithMessageEvents configures the Handler to record the specified events // (span.AddEvent) on spans. By default only summary attributes are added at the // end of the request. // // Valid events are: // - ReceivedEvents: Record the number of bytes read after every gRPC read operation. // - SentEvents: Record the number of bytes written after every gRPC write operation. func WithMessageEvents(events ...Event) Option { return messageEventsProviderOption{events: events} } type spanStartOption struct{ opts []trace.SpanStartOption } func (o spanStartOption) apply(c *config) { c.SpanStartOptions = append(c.SpanStartOptions, o.opts...) } // WithSpanOptions configures an additional set of // trace.SpanOptions, which are applied to each new span. func WithSpanOptions(opts ...trace.SpanStartOption) Option { return spanStartOption{opts} } type spanAttributesOption struct{ a []attribute.KeyValue } func (o spanAttributesOption) apply(c *config) { if o.a != nil { c.SpanAttributes = o.a } } // WithSpanAttributes returns an Option to add custom attributes to the spans. func WithSpanAttributes(a ...attribute.KeyValue) Option { return spanAttributesOption{a: a} } type metricAttributesOption struct{ a []attribute.KeyValue } func (o metricAttributesOption) apply(c *config) { if o.a != nil { c.MetricAttributes = o.a } } // WithMetricAttributes returns an Option to add custom attributes to the metrics. func WithMetricAttributes(a ...attribute.KeyValue) Option { return metricAttributesOption{a: a} } config_test.go000066400000000000000000000024501470323427300363530ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/google.golang.org/grpc/otelgrpc// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelgrpc import ( "context" "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/metric/embedded" ) func TestNilInstruments(t *testing.T) { mp := meterProvider{} c := newConfig([]Option{WithMeterProvider(mp)}, "test") ctx := context.Background() assert.NotPanics(t, func() { c.rpcDuration.Record(ctx, 0) }, "rpcDuration") assert.NotPanics(t, func() { c.rpcRequestSize.Record(ctx, 0) }, "rpcRequestSize") assert.NotPanics(t, func() { c.rpcResponseSize.Record(ctx, 0) }, "rpcResponseSize") assert.NotPanics(t, func() { c.rpcRequestsPerRPC.Record(ctx, 0) }, "rpcRequestsPerRPC") assert.NotPanics(t, func() { c.rpcResponsesPerRPC.Record(ctx, 0) }, "rpcResponsesPerRPC") } type meterProvider struct { embedded.MeterProvider } func (meterProvider) Meter(string, ...metric.MeterOption) metric.Meter { return meter{} } type meter struct { // Panic for non-implemented methods. metric.Meter } func (meter) Int64Histogram(string, ...metric.Int64HistogramOption) (metric.Int64Histogram, error) { return nil, assert.AnError } func (meter) Float64Histogram(string, ...metric.Float64HistogramOption) (metric.Float64Histogram, error) { return nil, assert.AnError } doc.go000066400000000000000000000006541470323427300346200ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/google.golang.org/grpc/otelgrpc// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 /* Package otelgrpc is the instrumentation library for [google.golang.org/grpc]. Use [NewClientHandler] with [grpc.WithStatsHandler] to instrument a gRPC client. Use [NewServerHandler] with [grpc.StatsHandler] to instrument a gRPC server. */ package otelgrpc // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" example/000077500000000000000000000000001470323427300351525ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/google.golang.org/grpc/otelgrpcREADME.md000066400000000000000000000005421470323427300364320ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/google.golang.org/grpc/otelgrpc/example# gRPC Tracing Example Traces client and server calls via interceptors. ### Compile .proto Only required if the service definition (.proto) changes. ```sh # protobuf v1.3.2 protoc -I api --go_out=plugins=grpc,paths=source_relative:./api api/hello-service.proto ``` ### Run server ```sh go run ./server ``` ### Run client ```sh go run ./client ```api/000077500000000000000000000000001470323427300357235ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/google.golang.org/grpc/otelgrpc/examplehello-service.pb.go000066400000000000000000000262411470323427300414200ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/google.golang.org/grpc/otelgrpc/example/api// Code generated by protoc-gen-go. DO NOT EDIT. // source: hello-service.proto /* Package api is a generated protocol buffer package. It is generated from these files: hello-service.proto It has these top-level messages: HelloRequest HelloResponse */ package api import proto "github.com/golang/protobuf/proto" import fmt "fmt" import math "math" import ( context "golang.org/x/net/context" grpc "google.golang.org/grpc" ) // Reference imports to suppress errors if they are not otherwise used. var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. // A compilation error at this line likely means your copy of the // proto package needs to be updated. const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package type HelloRequest struct { Greeting string `protobuf:"bytes,1,opt,name=greeting" json:"greeting,omitempty"` } func (m *HelloRequest) Reset() { *m = HelloRequest{} } func (m *HelloRequest) String() string { return proto.CompactTextString(m) } func (*HelloRequest) ProtoMessage() {} func (*HelloRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } func (m *HelloRequest) GetGreeting() string { if m != nil { return m.Greeting } return "" } type HelloResponse struct { Reply string `protobuf:"bytes,1,opt,name=reply" json:"reply,omitempty"` } func (m *HelloResponse) Reset() { *m = HelloResponse{} } func (m *HelloResponse) String() string { return proto.CompactTextString(m) } func (*HelloResponse) ProtoMessage() {} func (*HelloResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } func (m *HelloResponse) GetReply() string { if m != nil { return m.Reply } return "" } func init() { proto.RegisterType((*HelloRequest)(nil), "api.HelloRequest") proto.RegisterType((*HelloResponse)(nil), "api.HelloResponse") } // Reference imports to suppress errors if they are not otherwise used. var _ context.Context var _ grpc.ClientConn // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. const _ = grpc.SupportPackageIsVersion4 // Client API for HelloService service type HelloServiceClient interface { SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloResponse, error) SayHelloServerStream(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (HelloService_SayHelloServerStreamClient, error) SayHelloClientStream(ctx context.Context, opts ...grpc.CallOption) (HelloService_SayHelloClientStreamClient, error) SayHelloBidiStream(ctx context.Context, opts ...grpc.CallOption) (HelloService_SayHelloBidiStreamClient, error) } type helloServiceClient struct { cc *grpc.ClientConn } func NewHelloServiceClient(cc *grpc.ClientConn) HelloServiceClient { return &helloServiceClient{cc} } func (c *helloServiceClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloResponse, error) { out := new(HelloResponse) err := grpc.Invoke(ctx, "/api.HelloService/SayHello", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } func (c *helloServiceClient) SayHelloServerStream(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (HelloService_SayHelloServerStreamClient, error) { stream, err := grpc.NewClientStream(ctx, &_HelloService_serviceDesc.Streams[0], c.cc, "/api.HelloService/SayHelloServerStream", opts...) if err != nil { return nil, err } x := &helloServiceSayHelloServerStreamClient{stream} if err := x.ClientStream.SendMsg(in); err != nil { return nil, err } if err := x.ClientStream.CloseSend(); err != nil { return nil, err } return x, nil } type HelloService_SayHelloServerStreamClient interface { Recv() (*HelloResponse, error) grpc.ClientStream } type helloServiceSayHelloServerStreamClient struct { grpc.ClientStream } func (x *helloServiceSayHelloServerStreamClient) Recv() (*HelloResponse, error) { m := new(HelloResponse) if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err } return m, nil } func (c *helloServiceClient) SayHelloClientStream(ctx context.Context, opts ...grpc.CallOption) (HelloService_SayHelloClientStreamClient, error) { stream, err := grpc.NewClientStream(ctx, &_HelloService_serviceDesc.Streams[1], c.cc, "/api.HelloService/SayHelloClientStream", opts...) if err != nil { return nil, err } x := &helloServiceSayHelloClientStreamClient{stream} return x, nil } type HelloService_SayHelloClientStreamClient interface { Send(*HelloRequest) error CloseAndRecv() (*HelloResponse, error) grpc.ClientStream } type helloServiceSayHelloClientStreamClient struct { grpc.ClientStream } func (x *helloServiceSayHelloClientStreamClient) Send(m *HelloRequest) error { return x.ClientStream.SendMsg(m) } func (x *helloServiceSayHelloClientStreamClient) CloseAndRecv() (*HelloResponse, error) { if err := x.ClientStream.CloseSend(); err != nil { return nil, err } m := new(HelloResponse) if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err } return m, nil } func (c *helloServiceClient) SayHelloBidiStream(ctx context.Context, opts ...grpc.CallOption) (HelloService_SayHelloBidiStreamClient, error) { stream, err := grpc.NewClientStream(ctx, &_HelloService_serviceDesc.Streams[2], c.cc, "/api.HelloService/SayHelloBidiStream", opts...) if err != nil { return nil, err } x := &helloServiceSayHelloBidiStreamClient{stream} return x, nil } type HelloService_SayHelloBidiStreamClient interface { Send(*HelloRequest) error Recv() (*HelloResponse, error) grpc.ClientStream } type helloServiceSayHelloBidiStreamClient struct { grpc.ClientStream } func (x *helloServiceSayHelloBidiStreamClient) Send(m *HelloRequest) error { return x.ClientStream.SendMsg(m) } func (x *helloServiceSayHelloBidiStreamClient) Recv() (*HelloResponse, error) { m := new(HelloResponse) if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err } return m, nil } // Server API for HelloService service type HelloServiceServer interface { SayHello(context.Context, *HelloRequest) (*HelloResponse, error) SayHelloServerStream(*HelloRequest, HelloService_SayHelloServerStreamServer) error SayHelloClientStream(HelloService_SayHelloClientStreamServer) error SayHelloBidiStream(HelloService_SayHelloBidiStreamServer) error } func RegisterHelloServiceServer(s *grpc.Server, srv HelloServiceServer) { s.RegisterService(&_HelloService_serviceDesc, srv) } func _HelloService_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(HelloRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(HelloServiceServer).SayHello(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/api.HelloService/SayHello", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(HelloServiceServer).SayHello(ctx, req.(*HelloRequest)) } return interceptor(ctx, in, info, handler) } func _HelloService_SayHelloServerStream_Handler(srv interface{}, stream grpc.ServerStream) error { m := new(HelloRequest) if err := stream.RecvMsg(m); err != nil { return err } return srv.(HelloServiceServer).SayHelloServerStream(m, &helloServiceSayHelloServerStreamServer{stream}) } type HelloService_SayHelloServerStreamServer interface { Send(*HelloResponse) error grpc.ServerStream } type helloServiceSayHelloServerStreamServer struct { grpc.ServerStream } func (x *helloServiceSayHelloServerStreamServer) Send(m *HelloResponse) error { return x.ServerStream.SendMsg(m) } func _HelloService_SayHelloClientStream_Handler(srv interface{}, stream grpc.ServerStream) error { return srv.(HelloServiceServer).SayHelloClientStream(&helloServiceSayHelloClientStreamServer{stream}) } type HelloService_SayHelloClientStreamServer interface { SendAndClose(*HelloResponse) error Recv() (*HelloRequest, error) grpc.ServerStream } type helloServiceSayHelloClientStreamServer struct { grpc.ServerStream } func (x *helloServiceSayHelloClientStreamServer) SendAndClose(m *HelloResponse) error { return x.ServerStream.SendMsg(m) } func (x *helloServiceSayHelloClientStreamServer) Recv() (*HelloRequest, error) { m := new(HelloRequest) if err := x.ServerStream.RecvMsg(m); err != nil { return nil, err } return m, nil } func _HelloService_SayHelloBidiStream_Handler(srv interface{}, stream grpc.ServerStream) error { return srv.(HelloServiceServer).SayHelloBidiStream(&helloServiceSayHelloBidiStreamServer{stream}) } type HelloService_SayHelloBidiStreamServer interface { Send(*HelloResponse) error Recv() (*HelloRequest, error) grpc.ServerStream } type helloServiceSayHelloBidiStreamServer struct { grpc.ServerStream } func (x *helloServiceSayHelloBidiStreamServer) Send(m *HelloResponse) error { return x.ServerStream.SendMsg(m) } func (x *helloServiceSayHelloBidiStreamServer) Recv() (*HelloRequest, error) { m := new(HelloRequest) if err := x.ServerStream.RecvMsg(m); err != nil { return nil, err } return m, nil } var _HelloService_serviceDesc = grpc.ServiceDesc{ ServiceName: "api.HelloService", HandlerType: (*HelloServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "SayHello", Handler: _HelloService_SayHello_Handler, }, }, Streams: []grpc.StreamDesc{ { StreamName: "SayHelloServerStream", Handler: _HelloService_SayHelloServerStream_Handler, ServerStreams: true, }, { StreamName: "SayHelloClientStream", Handler: _HelloService_SayHelloClientStream_Handler, ClientStreams: true, }, { StreamName: "SayHelloBidiStream", Handler: _HelloService_SayHelloBidiStream_Handler, ServerStreams: true, ClientStreams: true, }, }, Metadata: "hello-service.proto", } func init() { proto.RegisterFile("hello-service.proto", fileDescriptor0) } var fileDescriptor0 = []byte{ // 192 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0xce, 0x48, 0xcd, 0xc9, 0xc9, 0xd7, 0x2d, 0x4e, 0x2d, 0x2a, 0xcb, 0x4c, 0x4e, 0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x4e, 0x2c, 0xc8, 0x54, 0xd2, 0xe2, 0xe2, 0xf1, 0x00, 0xc9, 0x05, 0xa5, 0x16, 0x96, 0xa6, 0x16, 0x97, 0x08, 0x49, 0x71, 0x71, 0xa4, 0x17, 0xa5, 0xa6, 0x96, 0x64, 0xe6, 0xa5, 0x4b, 0x30, 0x2a, 0x30, 0x6a, 0x70, 0x06, 0xc1, 0xf9, 0x4a, 0xaa, 0x5c, 0xbc, 0x50, 0xb5, 0xc5, 0x05, 0xf9, 0x79, 0xc5, 0xa9, 0x42, 0x22, 0x5c, 0xac, 0x45, 0xa9, 0x05, 0x39, 0x95, 0x50, 0x95, 0x10, 0x8e, 0x51, 0x0b, 0x13, 0xd4, 0xcc, 0x60, 0x88, 0x75, 0x42, 0x86, 0x5c, 0x1c, 0xc1, 0x89, 0x95, 0x60, 0x21, 0x21, 0x41, 0xbd, 0xc4, 0x82, 0x4c, 0x3d, 0x64, 0x2b, 0xa5, 0x84, 0x90, 0x85, 0xa0, 0x26, 0xdb, 0x73, 0x89, 0xc0, 0xb4, 0x80, 0x4c, 0x49, 0x2d, 0x0a, 0x2e, 0x29, 0x4a, 0x4d, 0xcc, 0x25, 0x52, 0xbb, 0x01, 0x23, 0xb2, 0x01, 0xce, 0x39, 0x99, 0xa9, 0x79, 0x25, 0x24, 0x19, 0xa0, 0x01, 0x32, 0x40, 0x08, 0x66, 0x80, 0x53, 0x66, 0x4a, 0x26, 0x89, 0xda, 0x0d, 0x18, 0x93, 0xd8, 0xc0, 0xa1, 0x6c, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0x0e, 0xd5, 0x1c, 0xd2, 0x7c, 0x01, 0x00, 0x00, } hello-service.proto000066400000000000000000000010061470323427300415460ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/google.golang.org/grpc/otelgrpc/example/api// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 syntax = "proto3"; package api; service HelloService { rpc SayHello (HelloRequest) returns (HelloResponse); rpc SayHelloServerStream (HelloRequest) returns (stream HelloResponse); rpc SayHelloClientStream (stream HelloRequest) returns (HelloResponse); rpc SayHelloBidiStream (stream HelloRequest) returns (stream HelloResponse); } message HelloRequest { string greeting = 1; } message HelloResponse { string reply = 1; } client/000077500000000000000000000000001470323427300364305ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/google.golang.org/grpc/otelgrpc/examplemain.go000066400000000000000000000114631470323427300377100ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/google.golang.org/grpc/otelgrpc/example/client// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package main import ( "context" "errors" "fmt" "io" "log" "time" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/example/api" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/example/config" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/metadata" ) func main() { tp, err := config.Init() if err != nil { log.Fatal(err) } defer func() { if err := tp.Shutdown(context.Background()); err != nil { log.Printf("Error shutting down tracer provider: %v", err) } }() var conn *grpc.ClientConn conn, err = grpc.NewClient("127.0.0.1:7777", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithStatsHandler(otelgrpc.NewClientHandler()), ) if err != nil { log.Fatalf("did not connect: %s", err) } defer func() { _ = conn.Close() }() c := api.NewHelloServiceClient(conn) if err := callSayHello(c); err != nil { log.Fatal(err) } if err := callSayHelloClientStream(c); err != nil { log.Fatal(err) } if err := callSayHelloServerStream(c); err != nil { log.Fatal(err) } if err := callSayHelloBidiStream(c); err != nil { log.Fatal(err) } time.Sleep(10 * time.Millisecond) } func callSayHello(c api.HelloServiceClient) error { md := metadata.Pairs( "timestamp", time.Now().Format(time.StampNano), "client-id", "web-api-client-us-east-1", "user-id", "some-test-user-id", ) ctx := metadata.NewOutgoingContext(context.Background(), md) response, err := c.SayHello(ctx, &api.HelloRequest{Greeting: "World"}) if err != nil { return fmt.Errorf("calling SayHello: %w", err) } log.Printf("Response from server: %s", response.Reply) return nil } func callSayHelloClientStream(c api.HelloServiceClient) error { md := metadata.Pairs( "timestamp", time.Now().Format(time.StampNano), "client-id", "web-api-client-us-east-1", "user-id", "some-test-user-id", ) ctx := metadata.NewOutgoingContext(context.Background(), md) stream, err := c.SayHelloClientStream(ctx) if err != nil { return fmt.Errorf("opening SayHelloClientStream: %w", err) } for i := 0; i < 5; i++ { err := stream.Send(&api.HelloRequest{Greeting: "World"}) time.Sleep(time.Duration(i*50) * time.Millisecond) if err != nil { return fmt.Errorf("sending to SayHelloClientStream: %w", err) } } response, err := stream.CloseAndRecv() if err != nil { return fmt.Errorf("closing SayHelloClientStream: %w", err) } log.Printf("Response from server: %s", response.Reply) return nil } func callSayHelloServerStream(c api.HelloServiceClient) error { md := metadata.Pairs( "timestamp", time.Now().Format(time.StampNano), "client-id", "web-api-client-us-east-1", "user-id", "some-test-user-id", ) ctx := metadata.NewOutgoingContext(context.Background(), md) stream, err := c.SayHelloServerStream(ctx, &api.HelloRequest{Greeting: "World"}) if err != nil { return fmt.Errorf("opening SayHelloServerStream: %w", err) } for { response, err := stream.Recv() if errors.Is(err, io.EOF) { break } else if err != nil { return fmt.Errorf("receiving from SayHelloServerStream: %w", err) } log.Printf("Response from server: %s", response.Reply) time.Sleep(50 * time.Millisecond) } return nil } func callSayHelloBidiStream(c api.HelloServiceClient) error { md := metadata.Pairs( "timestamp", time.Now().Format(time.StampNano), "client-id", "web-api-client-us-east-1", "user-id", "some-test-user-id", ) ctx := metadata.NewOutgoingContext(context.Background(), md) stream, err := c.SayHelloBidiStream(ctx) if err != nil { return fmt.Errorf("opening SayHelloBidiStream: %w", err) } serverClosed := make(chan struct{}) clientClosed := make(chan struct{}) go func() { for i := 0; i < 5; i++ { err := stream.Send(&api.HelloRequest{Greeting: "World"}) if err != nil { // nolint: revive // This acts as its own main func. log.Fatalf("Error when sending to SayHelloBidiStream: %s", err) } time.Sleep(50 * time.Millisecond) } err := stream.CloseSend() if err != nil { // nolint: revive // This acts as its own main func. log.Fatalf("Error when closing SayHelloBidiStream: %s", err) } clientClosed <- struct{}{} }() go func() { for { response, err := stream.Recv() if errors.Is(err, io.EOF) { break } else if err != nil { // nolint: revive // This acts as its own main func. log.Fatalf("Error when receiving from SayHelloBidiStream: %s", err) } log.Printf("Response from server: %s", response.Reply) time.Sleep(50 * time.Millisecond) } serverClosed <- struct{}{} }() // Wait until client and server both closed the connection. <-clientClosed <-serverClosed return nil } config/000077500000000000000000000000001470323427300364175ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/google.golang.org/grpc/otelgrpc/exampleconfig.go000066400000000000000000000015631470323427300402200ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/google.golang.org/grpc/otelgrpc/example/config// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package config // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/example/config" import ( "go.opentelemetry.io/otel" stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/propagation" sdktrace "go.opentelemetry.io/otel/sdk/trace" ) // Init configures an OpenTelemetry exporter and trace provider. func Init() (*sdktrace.TracerProvider, error) { exporter, err := stdout.New(stdout.WithPrettyPrint()) if err != nil { return nil, err } tp := sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithBatcher(exporter), ) otel.SetTracerProvider(tp) otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) return tp, nil } go.mod000066400000000000000000000017121470323427300362610ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/google.golang.org/grpc/otelgrpc/examplemodule go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/example go 1.22 replace go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc => ../ require ( github.com/golang/protobuf v1.5.4 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.56.0 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0 go.opentelemetry.io/otel/sdk v1.31.0 go.opentelemetry.io/otel/trace v1.31.0 golang.org/x/net v0.30.0 google.golang.org/grpc v1.67.1 ) require ( github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect golang.org/x/sys v0.26.0 // indirect golang.org/x/text v0.19.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 // indirect google.golang.org/protobuf v1.35.1 // indirect ) go.sum000066400000000000000000000067761470323427300363250ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/google.golang.org/grpc/otelgrpc/examplegithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0 h1:UGZ1QwZWY67Z6BmckTU+9Rxn04m2bD3gD6Mk0OIOCPk= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0/go.mod h1:fcwWuDuaObkkChiDlhEpSq9+X1C0omv+s5mBtToAQ64= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 h1:QCqS/PdaHTSWGvupk2F/ehwHtGc0/GYkT+3GAcR1CCc= google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= server/000077500000000000000000000000001470323427300364605ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/google.golang.org/grpc/otelgrpc/examplemain.go000066400000000000000000000057721470323427300377460ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/google.golang.org/grpc/otelgrpc/example/server// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package main import ( "context" "errors" "fmt" "io" "log" "net" "time" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/example/api" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/example/config" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" "google.golang.org/grpc" ) var tracer = otel.Tracer("grpc-example") // server is used to implement api.HelloServiceServer. type server struct { api.HelloServiceServer } // SayHello implements api.HelloServiceServer. func (s *server) SayHello(ctx context.Context, in *api.HelloRequest) (*api.HelloResponse, error) { log.Printf("Received: %v\n", in.GetGreeting()) s.workHard(ctx) time.Sleep(50 * time.Millisecond) return &api.HelloResponse{Reply: "Hello " + in.Greeting}, nil } func (s *server) workHard(ctx context.Context) { _, span := tracer.Start(ctx, "workHard", trace.WithAttributes(attribute.String("extra.key", "extra.value"))) defer span.End() time.Sleep(50 * time.Millisecond) } func (s *server) SayHelloServerStream(in *api.HelloRequest, out api.HelloService_SayHelloServerStreamServer) error { log.Printf("Received: %v\n", in.GetGreeting()) for i := 0; i < 5; i++ { err := out.Send(&api.HelloResponse{Reply: "Hello " + in.Greeting}) if err != nil { return err } time.Sleep(time.Duration(i*50) * time.Millisecond) } return nil } func (s *server) SayHelloClientStream(stream api.HelloService_SayHelloClientStreamServer) error { i := 0 for { in, err := stream.Recv() if errors.Is(err, io.EOF) { break } else if err != nil { log.Printf("Non EOF error: %v\n", err) return err } log.Printf("Received: %v\n", in.GetGreeting()) i++ } time.Sleep(50 * time.Millisecond) return stream.SendAndClose(&api.HelloResponse{Reply: fmt.Sprintf("Hello (%v times)", i)}) } func (s *server) SayHelloBidiStream(stream api.HelloService_SayHelloBidiStreamServer) error { for { in, err := stream.Recv() if errors.Is(err, io.EOF) { break } else if err != nil { log.Printf("Non EOF error: %v\n", err) return err } time.Sleep(50 * time.Millisecond) log.Printf("Received: %v\n", in.GetGreeting()) err = stream.Send(&api.HelloResponse{Reply: "Hello " + in.Greeting}) if err != nil { return err } } return nil } func main() { tp, err := config.Init() if err != nil { log.Fatal(err) } defer func() { if err := tp.Shutdown(context.Background()); err != nil { log.Printf("Error shutting down tracer provider: %v", err) } }() lis, err := net.Listen("tcp", "127.0.0.1:7777") if err != nil { log.Fatalf("failed to listen: %v", err) } s := grpc.NewServer( grpc.StatsHandler(otelgrpc.NewServerHandler()), ) api.RegisterHelloServiceServer(s, &server{}) if err := s.Serve(lis); err != nil { log.Fatalf("failed to serve: %v", err) } } example_test.go000066400000000000000000000006771470323427300365520ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/google.golang.org/grpc/otelgrpc// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelgrpc_test import ( "google.golang.org/grpc" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" ) func ExampleNewClientHandler() { _, _ = grpc.NewClient("localhost", grpc.WithStatsHandler(otelgrpc.NewClientHandler())) } func ExampleNewServerHandler() { _ = grpc.NewServer(grpc.StatsHandler(otelgrpc.NewServerHandler())) } filters/000077500000000000000000000000001470323427300351675ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/google.golang.org/grpc/otelgrpcfilters.go000066400000000000000000000071211470323427300371670ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/google.golang.org/grpc/otelgrpc/filters// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package filters provides a set of filters useful with the // [otelgrpc.WithFilter] option to control which inbound requests are instrumented. package filters // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/filters" import ( "path" "strings" "google.golang.org/grpc/stats" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" ) type gRPCPath struct { service string method string } // splitFullMethod splits path defined in gRPC protocol // and returns as gRPCPath object that has divided service and method names // https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md // If name is not FullMethod, returned gRPCPath has empty service field. func splitFullMethod(i *stats.RPCTagInfo) gRPCPath { s, m := path.Split(i.FullMethodName) if s != "" { s = path.Clean(s) s = strings.TrimLeft(s, "/") } return gRPCPath{ service: s, method: m, } } // Any takes a list of Filters and returns a Filter that // returns true if any Filter in the list returns true. func Any(fs ...otelgrpc.Filter) otelgrpc.Filter { return func(i *stats.RPCTagInfo) bool { for _, f := range fs { if f(i) { return true } } return false } } // All takes a list of Filters and returns a Filter that // returns true only if all Filters in the list return true. func All(fs ...otelgrpc.Filter) otelgrpc.Filter { return func(i *stats.RPCTagInfo) bool { for _, f := range fs { if !f(i) { return false } } return true } } // None takes a list of Filters and returns a Filter that returns // true only if none of the Filters in the list return true. func None(fs ...otelgrpc.Filter) otelgrpc.Filter { return Not(Any(fs...)) } // Not provides a convenience mechanism for inverting a Filter. func Not(f otelgrpc.Filter) otelgrpc.Filter { return func(i *stats.RPCTagInfo) bool { return !f(i) } } // MethodName returns a Filter that returns true if the request's // method name matches the provided string n. func MethodName(n string) otelgrpc.Filter { return func(i *stats.RPCTagInfo) bool { p := splitFullMethod(i) return p.method == n } } // MethodPrefix returns a Filter that returns true if the request's // method starts with the provided string pre. func MethodPrefix(pre string) otelgrpc.Filter { return func(i *stats.RPCTagInfo) bool { p := splitFullMethod(i) return strings.HasPrefix(p.method, pre) } } // FullMethodName returns a Filter that returns true if the request's // full RPC method string, i.e. /package.service/method, starts with // the provided string n. func FullMethodName(n string) otelgrpc.Filter { return func(i *stats.RPCTagInfo) bool { return i.FullMethodName == n } } // ServiceName returns a Filter that returns true if the request's // service name, i.e. package.service, matches s. func ServiceName(s string) otelgrpc.Filter { return func(i *stats.RPCTagInfo) bool { p := splitFullMethod(i) return p.service == s } } // ServicePrefix returns a Filter that returns true if the request's // service name, i.e. package.service, starts with the provided string pre. func ServicePrefix(pre string) otelgrpc.Filter { return func(i *stats.RPCTagInfo) bool { p := splitFullMethod(i) return strings.HasPrefix(p.service, pre) } } // HealthCheck returns a Filter that returns true if the request's // service name is health check defined by gRPC Health Checking Protocol. // https://github.com/grpc/grpc/blob/master/doc/health-checking.md func HealthCheck() otelgrpc.Filter { return ServicePrefix("grpc.health.v1.Health") } filters_test.go000066400000000000000000000153331470323427300402320ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/google.golang.org/grpc/otelgrpc/filters// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package filters // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/filters" import ( "testing" "google.golang.org/grpc/stats" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" ) type testCase struct { name string i *stats.RPCTagInfo f otelgrpc.Filter want bool } func dummyRPCTagInfo(n string) *stats.RPCTagInfo { return &stats.RPCTagInfo{ FullMethodName: n, } } func TestMethodName(t *testing.T) { const dummyFullMethodName = "/example.HelloService/Hello" tcs := []testCase{ { name: "true", i: dummyRPCTagInfo(dummyFullMethodName), f: MethodName("Hello"), want: true, }, { name: "false", i: dummyRPCTagInfo(dummyFullMethodName), f: MethodName("Goodbye"), want: false, }, } for _, tc := range tcs { out := tc.f(tc.i) if tc.want != out { t.Errorf("test case '%v' failed, wanted %v but obtained %v", tc.name, tc.want, out) } } } func TestMethodPrefix(t *testing.T) { const dummyFullMethodName = "/example.HelloService/FoobarHello" tcs := []testCase{ { name: "true", i: dummyRPCTagInfo(dummyFullMethodName), f: MethodPrefix("Foobar"), want: true, }, { name: "false", i: dummyRPCTagInfo(dummyFullMethodName), f: MethodPrefix("Barfoo"), want: false, }, } for _, tc := range tcs { out := tc.f(tc.i) if tc.want != out { t.Errorf("test case '%v' failed, wanted %v but obtained %v", tc.name, tc.want, out) } } } func TestFullMethodName(t *testing.T) { const dummyFullMethodName = "/example.HelloService/Hello" tcs := []testCase{ { name: "true", i: dummyRPCTagInfo(dummyFullMethodName), f: FullMethodName(dummyFullMethodName), want: true, }, { name: "false", i: dummyRPCTagInfo(dummyFullMethodName), f: FullMethodName("/example.HelloService/Goodbye"), want: false, }, } for _, tc := range tcs { out := tc.f(tc.i) if tc.want != out { t.Errorf("test case '%v' failed, wanted %v but obtained %v", tc.name, tc.want, out) } } } func TestServiceName(t *testing.T) { const dummyFullMethodName = "/example.HelloService/Hello" tcs := []testCase{ { name: "true", i: dummyRPCTagInfo(dummyFullMethodName), f: ServiceName("example.HelloService"), want: true, }, { name: "false", i: dummyRPCTagInfo(dummyFullMethodName), f: ServiceName("opentelemetry.HelloService"), want: false, }, } for _, tc := range tcs { out := tc.f(tc.i) if tc.want != out { t.Errorf("test case '%v' failed, wanted %v but obtained %v", tc.name, tc.want, out) } } } func TestServicePrefix(t *testing.T) { const dummyFullMethodName = "/example.HelloService/FoobarHello" tcs := []testCase{ { name: "true", i: dummyRPCTagInfo(dummyFullMethodName), f: ServicePrefix("example"), want: true, }, { name: "false", i: dummyRPCTagInfo(dummyFullMethodName), f: ServicePrefix("opentelemetry"), want: false, }, } for _, tc := range tcs { out := tc.f(tc.i) if tc.want != out { t.Errorf("test case '%v' failed, wanted %v but obtained %v", tc.name, tc.want, out) } } } func TestAny(t *testing.T) { const dummyFullMethodName = "/example.HelloService/FoobarHello" tcs := []testCase{ { name: "true && true", i: dummyRPCTagInfo(dummyFullMethodName), f: Any(MethodName("FoobarHello"), MethodPrefix("Foobar")), want: true, }, { name: "false && true", i: dummyRPCTagInfo(dummyFullMethodName), f: Any(MethodName("Hello"), MethodPrefix("Foobar")), want: true, }, { name: "false && false", i: dummyRPCTagInfo(dummyFullMethodName), f: Any(MethodName("Goodbye"), MethodPrefix("Barfoo")), want: false, }, } for _, tc := range tcs { out := tc.f(tc.i) if tc.want != out { t.Errorf("test case '%v' failed, wanted %v but obtained %v", tc.name, tc.want, out) } } } func TestAll(t *testing.T) { const dummyFullMethodName = "/example.HelloService/FoobarHello" tcs := []testCase{ { name: "true && true", i: dummyRPCTagInfo(dummyFullMethodName), f: All(MethodName("FoobarHello"), MethodPrefix("Foobar")), want: true, }, { name: "true && false", i: dummyRPCTagInfo(dummyFullMethodName), f: All(MethodName("FoobarHello"), MethodPrefix("Barfoo")), want: false, }, { name: "false && false", i: dummyRPCTagInfo(dummyFullMethodName), f: All(MethodName("FoobarGoodbye"), MethodPrefix("Barfoo")), want: false, }, } for _, tc := range tcs { out := tc.f(tc.i) if tc.want != out { t.Errorf("test case '%v' failed, wanted %v but obtained %v", tc.name, tc.want, out) } } } func TestNone(t *testing.T) { const dummyFullMethodName = "/example.HelloService/FoobarHello" tcs := []testCase{ { name: "true && true", i: dummyRPCTagInfo(dummyFullMethodName), f: None(MethodName("FoobarHello"), MethodPrefix("Foobar")), want: false, }, { name: "true && false", i: dummyRPCTagInfo(dummyFullMethodName), f: None(MethodName("FoobarHello"), MethodPrefix("Barfoo")), want: false, }, { name: "false && false", i: dummyRPCTagInfo(dummyFullMethodName), f: None(MethodName("FoobarGoodbye"), MethodPrefix("Barfoo")), want: true, }, } for _, tc := range tcs { out := tc.f(tc.i) if tc.want != out { t.Errorf("test case '%v' failed, wanted %v but obtained %v", tc.name, tc.want, out) } } } func TestNot(t *testing.T) { const dummyFullMethodName = "/example.HelloService/FoobarHello" tcs := []testCase{ { name: "methodname not", i: dummyRPCTagInfo(dummyFullMethodName), f: Not(MethodName("FoobarHello")), want: false, }, { name: "method prefix not", i: dummyRPCTagInfo(dummyFullMethodName), f: Not(MethodPrefix("FoobarHello")), want: false, }, { name: "not all(true && true)", i: dummyRPCTagInfo(dummyFullMethodName), f: Not(All(MethodName("FoobarHello"), MethodPrefix("Foobar"))), want: false, }, } for _, tc := range tcs { out := tc.f(tc.i) if tc.want != out { t.Errorf("test case '%v' failed, wanted %v but obtained %v", tc.name, tc.want, out) } } } func TestHealthCheck(t *testing.T) { const ( healthCheck = "/grpc.health.v1.Health/Check" dummyFullMethod = "/example.HelloService/FoobarHello" ) tcs := []testCase{ { name: "true", i: dummyRPCTagInfo(healthCheck), f: HealthCheck(), want: true, }, { name: "false", i: dummyRPCTagInfo(dummyFullMethod), f: HealthCheck(), want: false, }, } for _, tc := range tcs { out := tc.f(tc.i) if tc.want != out { t.Errorf("test case '%v' failed, wanted %v but obtained %v", tc.name, tc.want, out) } } } interceptor/000077500000000000000000000000001470323427300375255ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/google.golang.org/grpc/otelgrpc/filtersfilters.go000066400000000000000000000114741470323427300415330ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/google.golang.org/grpc/otelgrpc/filters/interceptor// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package interceptor provides a set of filters useful with the // [otelgrpc.WithInterceptorFilter] option to control which inbound requests are instrumented. // // Deprecated: Use filters package and [otelgrpc.WithFilter] instead. package interceptor // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/filters/interceptor" import ( "path" "strings" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" ) type gRPCPath struct { service string method string } // splitFullMethod splits path defined in gRPC protocol // and returns as gRPCPath object that has divided service and method names // https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md // If name is not FullMethod, returned gRPCPath has empty service field. func splitFullMethod(i *otelgrpc.InterceptorInfo) gRPCPath { var name string switch i.Type { case otelgrpc.UnaryServer: name = i.UnaryServerInfo.FullMethod case otelgrpc.StreamServer: name = i.StreamServerInfo.FullMethod case otelgrpc.UnaryClient, otelgrpc.StreamClient: name = i.Method default: name = i.Method } s, m := path.Split(name) if s != "" { s = path.Clean(s) s = strings.TrimLeft(s, "/") } return gRPCPath{ service: s, method: m, } } // Any takes a list of Filters and returns a Filter that // returns true if any Filter in the list returns true. // // Deprecated: Use stats handler instead. func Any(fs ...otelgrpc.InterceptorFilter) otelgrpc.InterceptorFilter { return func(i *otelgrpc.InterceptorInfo) bool { for _, f := range fs { if f(i) { return true } } return false } } // All takes a list of Filters and returns a Filter that // returns true only if all Filters in the list return true. // // Deprecated: Use stats handler instead. func All(fs ...otelgrpc.InterceptorFilter) otelgrpc.InterceptorFilter { return func(i *otelgrpc.InterceptorInfo) bool { for _, f := range fs { if !f(i) { return false } } return true } } // None takes a list of Filters and returns a Filter that returns // true only if none of the Filters in the list return true. // // Deprecated: Use stats handler instead. func None(fs ...otelgrpc.InterceptorFilter) otelgrpc.InterceptorFilter { return Not(Any(fs...)) } // Not provides a convenience mechanism for inverting a Filter. // // Deprecated: Use stats handler instead. func Not(f otelgrpc.InterceptorFilter) otelgrpc.InterceptorFilter { return func(i *otelgrpc.InterceptorInfo) bool { return !f(i) } } // MethodName returns a Filter that returns true if the request's // method name matches the provided string n. // // Deprecated: Use stats handler instead. func MethodName(n string) otelgrpc.InterceptorFilter { return func(i *otelgrpc.InterceptorInfo) bool { p := splitFullMethod(i) return p.method == n } } // MethodPrefix returns a Filter that returns true if the request's // method starts with the provided string pre. // // Deprecated: Use stats handler instead. func MethodPrefix(pre string) otelgrpc.InterceptorFilter { return func(i *otelgrpc.InterceptorInfo) bool { p := splitFullMethod(i) return strings.HasPrefix(p.method, pre) } } // FullMethodName returns a Filter that returns true if the request's // full RPC method string, i.e. /package.service/method, starts with // the provided string n. // // Deprecated: Use stats handler instead. func FullMethodName(n string) otelgrpc.InterceptorFilter { return func(i *otelgrpc.InterceptorInfo) bool { var fm string switch i.Type { case otelgrpc.UnaryClient, otelgrpc.StreamClient: fm = i.Method case otelgrpc.UnaryServer: fm = i.UnaryServerInfo.FullMethod case otelgrpc.StreamServer: fm = i.StreamServerInfo.FullMethod default: fm = i.Method } return fm == n } } // ServiceName returns a Filter that returns true if the request's // service name, i.e. package.service, matches s. // // Deprecated: Use stats handler instead. func ServiceName(s string) otelgrpc.InterceptorFilter { return func(i *otelgrpc.InterceptorInfo) bool { p := splitFullMethod(i) return p.service == s } } // ServicePrefix returns a Filter that returns true if the request's // service name, i.e. package.service, starts with the provided string pre. // // Deprecated: Use stats handler instead. func ServicePrefix(pre string) otelgrpc.InterceptorFilter { return func(i *otelgrpc.InterceptorInfo) bool { p := splitFullMethod(i) return strings.HasPrefix(p.service, pre) } } // HealthCheck returns a Filter that returns true if the request's // service name is health check defined by gRPC Health Checking Protocol. // https://github.com/grpc/grpc/blob/master/doc/health-checking.md // // Deprecated: Use stats handler instead. func HealthCheck() otelgrpc.InterceptorFilter { return ServicePrefix("grpc.health.v1.Health") } filters_test.go000066400000000000000000000316271470323427300425740ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/google.golang.org/grpc/otelgrpc/filters/interceptor// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package interceptor // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/filters/interceptor" import ( "testing" "google.golang.org/grpc" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" ) type testCase struct { name string i *otelgrpc.InterceptorInfo //nolint:staticcheck // Interceptors are deprecated and will be removed in the next release. f otelgrpc.InterceptorFilter want bool } func dummyUnaryServerInfo(n string) *grpc.UnaryServerInfo { return &grpc.UnaryServerInfo{ FullMethod: n, } } func dummyStreamServerInfo(n string) *grpc.StreamServerInfo { return &grpc.StreamServerInfo{ FullMethod: n, } } func TestMethodName(t *testing.T) { const dummyFullMethodName = "/example.HelloService/Hello" tcs := []testCase{ { name: "unary client interceptor", i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, f: MethodName("Hello"), want: true, }, { name: "stream client interceptor", i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.StreamClient}, f: MethodName("Hello"), want: true, }, { name: "unary server interceptor", i: &otelgrpc.InterceptorInfo{UnaryServerInfo: dummyUnaryServerInfo(dummyFullMethodName), Type: otelgrpc.UnaryServer}, f: MethodName("Hello"), want: true, }, { name: "stream server interceptor", i: &otelgrpc.InterceptorInfo{StreamServerInfo: dummyStreamServerInfo(dummyFullMethodName), Type: otelgrpc.StreamServer}, f: MethodName("Hello"), want: true, }, { name: "unary client interceptor fail", i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, f: MethodName("Goodbye"), want: false, }, } for _, tc := range tcs { out := tc.f(tc.i) if tc.want != out { t.Errorf("test case '%v' failed, wanted %v but obtained %v", tc.name, tc.want, out) } } } func TestMethodPrefix(t *testing.T) { const dummyFullMethodName = "/example.HelloService/FoobarHello" tcs := []testCase{ { name: "unary client interceptor", i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, f: MethodPrefix("Foobar"), want: true, }, { name: "stream client interceptor", i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.StreamClient}, f: MethodPrefix("Foobar"), want: true, }, { name: "unary server interceptor", i: &otelgrpc.InterceptorInfo{UnaryServerInfo: dummyUnaryServerInfo(dummyFullMethodName), Type: otelgrpc.UnaryServer}, f: MethodPrefix("Foobar"), want: true, }, { name: "stream server interceptor", i: &otelgrpc.InterceptorInfo{StreamServerInfo: dummyStreamServerInfo(dummyFullMethodName), Type: otelgrpc.StreamServer}, f: MethodPrefix("Foobar"), want: true, }, { name: "unary client interceptor fail", i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, f: MethodPrefix("Barfoo"), want: false, }, } for _, tc := range tcs { out := tc.f(tc.i) if tc.want != out { t.Errorf("test case '%v' failed, wanted %v but obtained %v", tc.name, tc.want, out) } } } func TestFullMethodName(t *testing.T) { const dummyFullMethodName = "/example.HelloService/Hello" tcs := []testCase{ { name: "unary client interceptor", i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, f: FullMethodName(dummyFullMethodName), want: true, }, { name: "stream client interceptor", i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.StreamClient}, f: FullMethodName(dummyFullMethodName), want: true, }, { name: "unary server interceptor", i: &otelgrpc.InterceptorInfo{UnaryServerInfo: dummyUnaryServerInfo(dummyFullMethodName), Type: otelgrpc.UnaryServer}, f: FullMethodName(dummyFullMethodName), want: true, }, { name: "stream server interceptor", i: &otelgrpc.InterceptorInfo{StreamServerInfo: dummyStreamServerInfo(dummyFullMethodName), Type: otelgrpc.StreamServer}, f: FullMethodName(dummyFullMethodName), want: true, }, { name: "unary client interceptor fail", i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, f: FullMethodName("/example.HelloService/Goodbye"), want: false, }, } for _, tc := range tcs { out := tc.f(tc.i) if tc.want != out { t.Errorf("test case '%v' failed, wanted %v but obtained %v", tc.name, tc.want, out) } } } func TestServiceName(t *testing.T) { const dummyFullMethodName = "/example.HelloService/Hello" tcs := []testCase{ { name: "unary client interceptor", i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, f: ServiceName("example.HelloService"), want: true, }, { name: "stream client interceptor", i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.StreamClient}, f: ServiceName("example.HelloService"), want: true, }, { name: "unary server interceptor", i: &otelgrpc.InterceptorInfo{UnaryServerInfo: dummyUnaryServerInfo(dummyFullMethodName), Type: otelgrpc.UnaryServer}, f: ServiceName("example.HelloService"), want: true, }, { name: "stream server interceptor", i: &otelgrpc.InterceptorInfo{StreamServerInfo: dummyStreamServerInfo(dummyFullMethodName), Type: otelgrpc.StreamServer}, f: ServiceName("example.HelloService"), want: true, }, { name: "unary client interceptor fail", i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, f: ServiceName("opentelemetry.HelloService"), want: false, }, } for _, tc := range tcs { out := tc.f(tc.i) if tc.want != out { t.Errorf("test case '%v' failed, wanted %v but obtained %v", tc.name, tc.want, out) } } } func TestServicePrefix(t *testing.T) { const dummyFullMethodName = "/example.HelloService/FoobarHello" tcs := []testCase{ { name: "unary client interceptor", i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, f: ServicePrefix("example"), want: true, }, { name: "stream client interceptor", i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.StreamClient}, f: ServicePrefix("example"), want: true, }, { name: "unary server interceptor", i: &otelgrpc.InterceptorInfo{UnaryServerInfo: dummyUnaryServerInfo(dummyFullMethodName), Type: otelgrpc.UnaryServer}, f: ServicePrefix("example"), want: true, }, { name: "stream server interceptor", i: &otelgrpc.InterceptorInfo{StreamServerInfo: dummyStreamServerInfo(dummyFullMethodName), Type: otelgrpc.StreamServer}, f: ServicePrefix("example"), want: true, }, { name: "unary client interceptor fail", i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, f: ServicePrefix("opentelemetry"), want: false, }, } for _, tc := range tcs { out := tc.f(tc.i) if tc.want != out { t.Errorf("test case '%v' failed, wanted %v but obtained %v", tc.name, tc.want, out) } } } func TestAny(t *testing.T) { const dummyFullMethodName = "/example.HelloService/FoobarHello" tcs := []testCase{ { name: "unary client interceptor true && true", i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, f: Any(MethodName("FoobarHello"), MethodPrefix("Foobar")), want: true, }, { name: "unary client interceptor false && true", i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, f: Any(MethodName("Hello"), MethodPrefix("Foobar")), want: true, }, { name: "unary client interceptor false && false", i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, f: Any(MethodName("Goodbye"), MethodPrefix("Barfoo")), want: false, }, } for _, tc := range tcs { out := tc.f(tc.i) if tc.want != out { t.Errorf("test case '%v' failed, wanted %v but obtained %v", tc.name, tc.want, out) } } } func TestAll(t *testing.T) { const dummyFullMethodName = "/example.HelloService/FoobarHello" tcs := []testCase{ { name: "unary client interceptor true && true", i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, f: All(MethodName("FoobarHello"), MethodPrefix("Foobar")), want: true, }, { name: "unary client interceptor true && false", i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, f: All(MethodName("FoobarHello"), MethodPrefix("Barfoo")), want: false, }, { name: "unary client interceptor false && false", i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, f: All(MethodName("FoobarGoodbye"), MethodPrefix("Barfoo")), want: false, }, } for _, tc := range tcs { out := tc.f(tc.i) if tc.want != out { t.Errorf("test case '%v' failed, wanted %v but obtained %v", tc.name, tc.want, out) } } } func TestNone(t *testing.T) { const dummyFullMethodName = "/example.HelloService/FoobarHello" tcs := []testCase{ { name: "unary client interceptor true && true", i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, f: None(MethodName("FoobarHello"), MethodPrefix("Foobar")), want: false, }, { name: "unary client interceptor true && false", i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, f: None(MethodName("FoobarHello"), MethodPrefix("Barfoo")), want: false, }, { name: "unary client interceptor false && false", i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, f: None(MethodName("FoobarGoodbye"), MethodPrefix("Barfoo")), want: true, }, } for _, tc := range tcs { out := tc.f(tc.i) if tc.want != out { t.Errorf("test case '%v' failed, wanted %v but obtained %v", tc.name, tc.want, out) } } } func TestNot(t *testing.T) { const dummyFullMethodName = "/example.HelloService/FoobarHello" tcs := []testCase{ { name: "methodname not", i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, f: Not(MethodName("FoobarHello")), want: false, }, { name: "method prefix not", i: &otelgrpc.InterceptorInfo{UnaryServerInfo: dummyUnaryServerInfo(dummyFullMethodName), Type: otelgrpc.UnaryServer}, f: Not(MethodPrefix("FoobarHello")), want: false, }, { name: "unary client interceptor not all(true && true)", i: &otelgrpc.InterceptorInfo{Method: dummyFullMethodName, Type: otelgrpc.UnaryClient}, f: Not(All(MethodName("FoobarHello"), MethodPrefix("Foobar"))), want: false, }, } for _, tc := range tcs { out := tc.f(tc.i) if tc.want != out { t.Errorf("test case '%v' failed, wanted %v but obtained %v", tc.name, tc.want, out) } } } func TestHealthCheck(t *testing.T) { const ( healthCheck = "/grpc.health.v1.Health/Check" dummyFullMethod = "/example.HelloService/FoobarHello" ) tcs := []testCase{ { name: "unary client interceptor healthcheck", i: &otelgrpc.InterceptorInfo{Method: healthCheck, Type: otelgrpc.UnaryClient}, f: HealthCheck(), want: true, }, { name: "stream client interceptor healthcheck", i: &otelgrpc.InterceptorInfo{Method: healthCheck, Type: otelgrpc.StreamClient}, f: HealthCheck(), want: true, }, { name: "unary server interceptor healthcheck", i: &otelgrpc.InterceptorInfo{UnaryServerInfo: dummyUnaryServerInfo(healthCheck), Type: otelgrpc.UnaryServer}, f: HealthCheck(), want: true, }, { name: "stream server interceptor healthcheck", i: &otelgrpc.InterceptorInfo{StreamServerInfo: dummyStreamServerInfo(healthCheck), Type: otelgrpc.StreamServer}, f: HealthCheck(), want: true, }, { name: "unary client interceptor", i: &otelgrpc.InterceptorInfo{Method: dummyFullMethod, Type: otelgrpc.UnaryClient}, f: HealthCheck(), want: false, }, { name: "stream client interceptor", i: &otelgrpc.InterceptorInfo{Method: dummyFullMethod, Type: otelgrpc.StreamClient}, f: HealthCheck(), want: false, }, { name: "unary server interceptor", i: &otelgrpc.InterceptorInfo{UnaryServerInfo: dummyUnaryServerInfo(dummyFullMethod), Type: otelgrpc.UnaryServer}, f: HealthCheck(), want: false, }, { name: "stream server interceptor", i: &otelgrpc.InterceptorInfo{StreamServerInfo: dummyStreamServerInfo(dummyFullMethod), Type: otelgrpc.StreamServer}, f: HealthCheck(), want: false, }, } for _, tc := range tcs { out := tc.f(tc.i) if tc.want != out { t.Errorf("test case '%v' failed, wanted %v but obtained %v", tc.name, tc.want, out) } } } go.mod000066400000000000000000000013741470323427300346320ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/google.golang.org/grpc/otelgrpcmodule go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc go 1.22 require ( github.com/stretchr/testify v1.9.0 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/metric v1.31.0 go.opentelemetry.io/otel/trace v1.31.0 google.golang.org/grpc v1.67.1 google.golang.org/protobuf v1.35.1 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect golang.org/x/net v0.30.0 // indirect golang.org/x/sys v0.26.0 // indirect golang.org/x/text v0.19.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) go.sum000066400000000000000000000057551470323427300346660ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/google.golang.org/grpc/otelgrpcgithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 h1:QCqS/PdaHTSWGvupk2F/ehwHtGc0/GYkT+3GAcR1CCc= google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= interceptor.go000066400000000000000000000313251470323427300364100ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/google.golang.org/grpc/otelgrpc// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelgrpc // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" // gRPC tracing middleware // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/rpc.md import ( "context" "errors" "io" "net" "strconv" "time" "google.golang.org/grpc" grpc_codes "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" "google.golang.org/grpc/peer" "google.golang.org/grpc/status" "google.golang.org/protobuf/proto" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/internal" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/metric" semconv "go.opentelemetry.io/otel/semconv/v1.17.0" "go.opentelemetry.io/otel/trace" ) type messageType attribute.KeyValue // Event adds an event of the messageType to the span associated with the // passed context with a message id. func (m messageType) Event(ctx context.Context, id int, _ interface{}) { span := trace.SpanFromContext(ctx) if !span.IsRecording() { return } span.AddEvent("message", trace.WithAttributes( attribute.KeyValue(m), RPCMessageIDKey.Int(id), )) } var ( messageSent = messageType(RPCMessageTypeSent) messageReceived = messageType(RPCMessageTypeReceived) ) // UnaryClientInterceptor returns a grpc.UnaryClientInterceptor suitable // for use in a grpc.NewClient call. // // Deprecated: Use [NewClientHandler] instead. func UnaryClientInterceptor(opts ...Option) grpc.UnaryClientInterceptor { cfg := newConfig(opts, "client") tracer := cfg.TracerProvider.Tracer( ScopeName, trace.WithInstrumentationVersion(Version()), ) return func( ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, callOpts ...grpc.CallOption, ) error { i := &InterceptorInfo{ Method: method, Type: UnaryClient, } if cfg.InterceptorFilter != nil && !cfg.InterceptorFilter(i) { return invoker(ctx, method, req, reply, cc, callOpts...) } name, attr, _ := telemetryAttributes(method, cc.Target()) startOpts := append([]trace.SpanStartOption{ trace.WithSpanKind(trace.SpanKindClient), trace.WithAttributes(attr...), }, cfg.SpanStartOptions..., ) ctx, span := tracer.Start( ctx, name, startOpts..., ) defer span.End() ctx = inject(ctx, cfg.Propagators) if cfg.SentEvent { messageSent.Event(ctx, 1, req) } err := invoker(ctx, method, req, reply, cc, callOpts...) if cfg.ReceivedEvent { messageReceived.Event(ctx, 1, reply) } if err != nil { s, _ := status.FromError(err) span.SetStatus(codes.Error, s.Message()) span.SetAttributes(statusCodeAttr(s.Code())) } else { span.SetAttributes(statusCodeAttr(grpc_codes.OK)) } return err } } // clientStream wraps around the embedded grpc.ClientStream, and intercepts the RecvMsg and // SendMsg method call. type clientStream struct { grpc.ClientStream desc *grpc.StreamDesc span trace.Span receivedEvent bool sentEvent bool receivedMessageID int sentMessageID int } var _ = proto.Marshal func (w *clientStream) RecvMsg(m interface{}) error { err := w.ClientStream.RecvMsg(m) if err == nil && !w.desc.ServerStreams { w.endSpan(nil) } else if errors.Is(err, io.EOF) { w.endSpan(nil) } else if err != nil { w.endSpan(err) } else { w.receivedMessageID++ if w.receivedEvent { messageReceived.Event(w.Context(), w.receivedMessageID, m) } } return err } func (w *clientStream) SendMsg(m interface{}) error { err := w.ClientStream.SendMsg(m) w.sentMessageID++ if w.sentEvent { messageSent.Event(w.Context(), w.sentMessageID, m) } if err != nil { w.endSpan(err) } return err } func (w *clientStream) Header() (metadata.MD, error) { md, err := w.ClientStream.Header() if err != nil { w.endSpan(err) } return md, err } func (w *clientStream) CloseSend() error { err := w.ClientStream.CloseSend() if err != nil { w.endSpan(err) } return err } func wrapClientStream(s grpc.ClientStream, desc *grpc.StreamDesc, span trace.Span, cfg *config) *clientStream { return &clientStream{ ClientStream: s, span: span, desc: desc, receivedEvent: cfg.ReceivedEvent, sentEvent: cfg.SentEvent, } } func (w *clientStream) endSpan(err error) { if err != nil { s, _ := status.FromError(err) w.span.SetStatus(codes.Error, s.Message()) w.span.SetAttributes(statusCodeAttr(s.Code())) } else { w.span.SetAttributes(statusCodeAttr(grpc_codes.OK)) } w.span.End() } // StreamClientInterceptor returns a grpc.StreamClientInterceptor suitable // for use in a grpc.NewClient call. // // Deprecated: Use [NewClientHandler] instead. func StreamClientInterceptor(opts ...Option) grpc.StreamClientInterceptor { cfg := newConfig(opts, "client") tracer := cfg.TracerProvider.Tracer( ScopeName, trace.WithInstrumentationVersion(Version()), ) return func( ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, callOpts ...grpc.CallOption, ) (grpc.ClientStream, error) { i := &InterceptorInfo{ Method: method, Type: StreamClient, } if cfg.InterceptorFilter != nil && !cfg.InterceptorFilter(i) { return streamer(ctx, desc, cc, method, callOpts...) } name, attr, _ := telemetryAttributes(method, cc.Target()) startOpts := append([]trace.SpanStartOption{ trace.WithSpanKind(trace.SpanKindClient), trace.WithAttributes(attr...), }, cfg.SpanStartOptions..., ) ctx, span := tracer.Start( ctx, name, startOpts..., ) ctx = inject(ctx, cfg.Propagators) s, err := streamer(ctx, desc, cc, method, callOpts...) if err != nil { grpcStatus, _ := status.FromError(err) span.SetStatus(codes.Error, grpcStatus.Message()) span.SetAttributes(statusCodeAttr(grpcStatus.Code())) span.End() return s, err } stream := wrapClientStream(s, desc, span, cfg) return stream, nil } } // UnaryServerInterceptor returns a grpc.UnaryServerInterceptor suitable // for use in a grpc.NewServer call. // // Deprecated: Use [NewServerHandler] instead. func UnaryServerInterceptor(opts ...Option) grpc.UnaryServerInterceptor { cfg := newConfig(opts, "server") tracer := cfg.TracerProvider.Tracer( ScopeName, trace.WithInstrumentationVersion(Version()), ) return func( ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler, ) (interface{}, error) { i := &InterceptorInfo{ UnaryServerInfo: info, Type: UnaryServer, } if cfg.InterceptorFilter != nil && !cfg.InterceptorFilter(i) { return handler(ctx, req) } ctx = extract(ctx, cfg.Propagators) name, attr, metricAttrs := telemetryAttributes(info.FullMethod, peerFromCtx(ctx)) startOpts := append([]trace.SpanStartOption{ trace.WithSpanKind(trace.SpanKindServer), trace.WithAttributes(attr...), }, cfg.SpanStartOptions..., ) ctx, span := tracer.Start( trace.ContextWithRemoteSpanContext(ctx, trace.SpanContextFromContext(ctx)), name, startOpts..., ) defer span.End() if cfg.ReceivedEvent { messageReceived.Event(ctx, 1, req) } before := time.Now() resp, err := handler(ctx, req) s, _ := status.FromError(err) if err != nil { statusCode, msg := serverStatus(s) span.SetStatus(statusCode, msg) if cfg.SentEvent { messageSent.Event(ctx, 1, s.Proto()) } } else { if cfg.SentEvent { messageSent.Event(ctx, 1, resp) } } grpcStatusCodeAttr := statusCodeAttr(s.Code()) span.SetAttributes(grpcStatusCodeAttr) // Use floating point division here for higher precision (instead of Millisecond method). elapsedTime := float64(time.Since(before)) / float64(time.Millisecond) metricAttrs = append(metricAttrs, grpcStatusCodeAttr) cfg.rpcDuration.Record(ctx, elapsedTime, metric.WithAttributeSet(attribute.NewSet(metricAttrs...))) return resp, err } } // serverStream wraps around the embedded grpc.ServerStream, and intercepts the RecvMsg and // SendMsg method call. type serverStream struct { grpc.ServerStream ctx context.Context receivedMessageID int sentMessageID int receivedEvent bool sentEvent bool } func (w *serverStream) Context() context.Context { return w.ctx } func (w *serverStream) RecvMsg(m interface{}) error { err := w.ServerStream.RecvMsg(m) if err == nil { w.receivedMessageID++ if w.receivedEvent { messageReceived.Event(w.Context(), w.receivedMessageID, m) } } return err } func (w *serverStream) SendMsg(m interface{}) error { err := w.ServerStream.SendMsg(m) w.sentMessageID++ if w.sentEvent { messageSent.Event(w.Context(), w.sentMessageID, m) } return err } func wrapServerStream(ctx context.Context, ss grpc.ServerStream, cfg *config) *serverStream { return &serverStream{ ServerStream: ss, ctx: ctx, receivedEvent: cfg.ReceivedEvent, sentEvent: cfg.SentEvent, } } // StreamServerInterceptor returns a grpc.StreamServerInterceptor suitable // for use in a grpc.NewServer call. // // Deprecated: Use [NewServerHandler] instead. func StreamServerInterceptor(opts ...Option) grpc.StreamServerInterceptor { cfg := newConfig(opts, "server") tracer := cfg.TracerProvider.Tracer( ScopeName, trace.WithInstrumentationVersion(Version()), ) return func( srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler, ) error { ctx := ss.Context() i := &InterceptorInfo{ StreamServerInfo: info, Type: StreamServer, } if cfg.InterceptorFilter != nil && !cfg.InterceptorFilter(i) { return handler(srv, wrapServerStream(ctx, ss, cfg)) } ctx = extract(ctx, cfg.Propagators) name, attr, _ := telemetryAttributes(info.FullMethod, peerFromCtx(ctx)) startOpts := append([]trace.SpanStartOption{ trace.WithSpanKind(trace.SpanKindServer), trace.WithAttributes(attr...), }, cfg.SpanStartOptions..., ) ctx, span := tracer.Start( trace.ContextWithRemoteSpanContext(ctx, trace.SpanContextFromContext(ctx)), name, startOpts..., ) defer span.End() err := handler(srv, wrapServerStream(ctx, ss, cfg)) if err != nil { s, _ := status.FromError(err) statusCode, msg := serverStatus(s) span.SetStatus(statusCode, msg) span.SetAttributes(statusCodeAttr(s.Code())) } else { span.SetAttributes(statusCodeAttr(grpc_codes.OK)) } return err } } // telemetryAttributes returns a span name and span and metric attributes from // the gRPC method and peer address. func telemetryAttributes(fullMethod, peerAddress string) (string, []attribute.KeyValue, []attribute.KeyValue) { name, methodAttrs := internal.ParseFullMethod(fullMethod) peerAttrs := peerAttr(peerAddress) attrs := make([]attribute.KeyValue, 0, 1+len(methodAttrs)+len(peerAttrs)) attrs = append(attrs, RPCSystemGRPC) attrs = append(attrs, methodAttrs...) metricAttrs := attrs[:1+len(methodAttrs)] attrs = append(attrs, peerAttrs...) return name, attrs, metricAttrs } // peerAttr returns attributes about the peer address. func peerAttr(addr string) []attribute.KeyValue { host, p, err := net.SplitHostPort(addr) if err != nil { return nil } if host == "" { host = "127.0.0.1" } port, err := strconv.Atoi(p) if err != nil { return nil } var attr []attribute.KeyValue if ip := net.ParseIP(host); ip != nil { attr = []attribute.KeyValue{ semconv.NetSockPeerAddr(host), semconv.NetSockPeerPort(port), } } else { attr = []attribute.KeyValue{ semconv.NetPeerName(host), semconv.NetPeerPort(port), } } return attr } // peerFromCtx returns a peer address from a context, if one exists. func peerFromCtx(ctx context.Context) string { p, ok := peer.FromContext(ctx) if !ok { return "" } return p.Addr.String() } // statusCodeAttr returns status code attribute based on given gRPC code. func statusCodeAttr(c grpc_codes.Code) attribute.KeyValue { return GRPCStatusCodeKey.Int64(int64(c)) } // serverStatus returns a span status code and message for a given gRPC // status code. It maps specific gRPC status codes to a corresponding span // status code and message. This function is intended for use on the server // side of a gRPC connection. // // If the gRPC status code is Unknown, DeadlineExceeded, Unimplemented, // Internal, Unavailable, or DataLoss, it returns a span status code of Error // and the message from the gRPC status. Otherwise, it returns a span status // code of Unset and an empty message. func serverStatus(grpcStatus *status.Status) (codes.Code, string) { switch grpcStatus.Code() { case grpc_codes.Unknown, grpc_codes.DeadlineExceeded, grpc_codes.Unimplemented, grpc_codes.Internal, grpc_codes.Unavailable, grpc_codes.DataLoss: return codes.Error, grpcStatus.Message() default: return codes.Unset, "" } } interceptorinfo.go000066400000000000000000000024161470323427300372630ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/google.golang.org/grpc/otelgrpc// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelgrpc // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" import ( "google.golang.org/grpc" ) // InterceptorType is the flag to define which gRPC interceptor // the InterceptorInfo object is. type InterceptorType uint8 const ( // UndefinedInterceptor is the type for the interceptor information that is not // well initialized or categorized to other types. UndefinedInterceptor InterceptorType = iota // UnaryClient is the type for grpc.UnaryClient interceptor. UnaryClient // StreamClient is the type for grpc.StreamClient interceptor. StreamClient // UnaryServer is the type for grpc.UnaryServer interceptor. UnaryServer // StreamServer is the type for grpc.StreamServer interceptor. StreamServer ) // InterceptorInfo is the union of some arguments to four types of // gRPC interceptors. type InterceptorInfo struct { // Method is method name registered to UnaryClient and StreamClient Method string // UnaryServerInfo is the metadata for UnaryServer UnaryServerInfo *grpc.UnaryServerInfo // StreamServerInfo if the metadata for StreamServer StreamServerInfo *grpc.StreamServerInfo // Type is the type for interceptor Type InterceptorType } internal/000077500000000000000000000000001470323427300353335ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/google.golang.org/grpc/otelgrpcparse.go000066400000000000000000000023431470323427300367760ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/google.golang.org/grpc/otelgrpc/internal// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package internal // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/internal" import ( "strings" "go.opentelemetry.io/otel/attribute" semconv "go.opentelemetry.io/otel/semconv/v1.17.0" ) // ParseFullMethod returns a span name following the OpenTelemetry semantic // conventions as well as all applicable span attribute.KeyValue attributes based // on a gRPC's FullMethod. // // Parsing is consistent with grpc-go implementation: // https://github.com/grpc/grpc-go/blob/v1.57.0/internal/grpcutil/method.go#L26-L39 func ParseFullMethod(fullMethod string) (string, []attribute.KeyValue) { if !strings.HasPrefix(fullMethod, "/") { // Invalid format, does not follow `/package.service/method`. return fullMethod, nil } name := fullMethod[1:] pos := strings.LastIndex(name, "/") if pos < 0 { // Invalid format, does not follow `/package.service/method`. return name, nil } service, method := name[:pos], name[pos+1:] var attrs []attribute.KeyValue if service != "" { attrs = append(attrs, semconv.RPCService(service)) } if method != "" { attrs = append(attrs, semconv.RPCMethod(method)) } return name, attrs } parse_test.go000066400000000000000000000025771470323427300400460ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/google.golang.org/grpc/otelgrpc/internal// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package internal // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/internal" import ( "testing" "go.opentelemetry.io/otel/attribute" semconv "go.opentelemetry.io/otel/semconv/v1.17.0" "github.com/stretchr/testify/assert" ) func TestParseFullMethod(t *testing.T) { cases := []struct { input string expectedName string expectedAttrs []attribute.KeyValue }{ { input: "no_slash/method", expectedName: "no_slash/method", }, { input: "/slash_but_no_second_slash", expectedName: "slash_but_no_second_slash", }, { input: "/service_only/", expectedName: "service_only/", expectedAttrs: []attribute.KeyValue{ semconv.RPCService("service_only"), }, }, { input: "//method_only", expectedName: "/method_only", expectedAttrs: []attribute.KeyValue{ semconv.RPCMethod("method_only"), }, }, { input: "/service/method", expectedName: "service/method", expectedAttrs: []attribute.KeyValue{ semconv.RPCService("service"), semconv.RPCMethod("method"), }, }, } for _, tc := range cases { t.Run(tc.input, func(t *testing.T) { name, attrs := ParseFullMethod(tc.input) assert.Equal(t, tc.expectedName, name) assert.Equal(t, tc.expectedAttrs, attrs) }) } } test/000077500000000000000000000000001470323427300363125ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/google.golang.org/grpc/otelgrpc/internaltest_utils.go000066400000000000000000000300741470323427300410440ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/google.golang.org/grpc/otelgrpc/internal/test// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 /* * * Copyright 2014 gRPC authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ // Package test contains functions used by interop client/server. // // Copied from https://github.com/grpc/grpc-go/tree/v1.61.0/interop // That package was not intended to be used by external code. // See https://github.com/open-telemetry/opentelemetry-go-contrib/issues/4896 package test // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/internal/test" import ( "context" "errors" "fmt" "io" "time" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/grpclog" "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" "google.golang.org/protobuf/proto" testpb "google.golang.org/grpc/interop/grpc_testing" ) var ( reqSizes = []int32{27182, 8, 1828, 45904} respSizes = []int32{31415, 9, 2653, 58979} largeReqSize = 271828 largeRespSize int32 = 314159 initialMetadataKey = "x-grpc-test-echo-initial" trailingMetadataKey = "x-grpc-test-echo-trailing-bin" logger = grpclog.Component("interop") ) // ClientNewPayload returns a payload of the given type and size. func ClientNewPayload(t testpb.PayloadType, size int) *testpb.Payload { if size < 0 { logger.Fatalf("Requested a response with invalid length %d", size) } body := make([]byte, size) switch t { case testpb.PayloadType_COMPRESSABLE: default: logger.Fatalf("Unsupported payload type: %d", t) } return &testpb.Payload{ Type: t, Body: body, } } // DoEmptyUnaryCall performs a unary RPC with empty request and response messages. func DoEmptyUnaryCall(ctx context.Context, tc testpb.TestServiceClient, args ...grpc.CallOption) { reply, err := tc.EmptyCall(ctx, &testpb.Empty{}, args...) if err != nil { logger.Fatal("/TestService/EmptyCall RPC failed: ", err) } if !proto.Equal(&testpb.Empty{}, reply) { logger.Fatalf("/TestService/EmptyCall receives %v, want %v", reply, testpb.Empty{}) } } // DoLargeUnaryCall performs a unary RPC with large payload in the request and response. func DoLargeUnaryCall(ctx context.Context, tc testpb.TestServiceClient, args ...grpc.CallOption) { pl := ClientNewPayload(testpb.PayloadType_COMPRESSABLE, largeReqSize) req := &testpb.SimpleRequest{ ResponseType: testpb.PayloadType_COMPRESSABLE, ResponseSize: largeRespSize, Payload: pl, } reply, err := tc.UnaryCall(ctx, req, args...) if err != nil { logger.Fatal("/TestService/UnaryCall RPC failed: ", err) } t := reply.GetPayload().GetType() s := len(reply.GetPayload().GetBody()) if t != testpb.PayloadType_COMPRESSABLE || s != int(largeRespSize) { logger.Fatalf("Got the reply with type %d len %d; want %d, %d", t, s, testpb.PayloadType_COMPRESSABLE, largeRespSize) } } // DoClientStreaming performs a client streaming RPC. func DoClientStreaming(ctx context.Context, tc testpb.TestServiceClient, args ...grpc.CallOption) { stream, err := tc.StreamingInputCall(ctx, args...) if err != nil { logger.Fatalf("%v.StreamingInputCall(_) = _, %v", tc, err) } var sum int32 for _, s := range reqSizes { pl := ClientNewPayload(testpb.PayloadType_COMPRESSABLE, int(s)) req := &testpb.StreamingInputCallRequest{ Payload: pl, } if err := stream.Send(req); err != nil { logger.Fatalf("%v has error %v while sending %v", stream, err, req) } sum += s } reply, err := stream.CloseAndRecv() if err != nil { logger.Fatalf("%v.CloseAndRecv() got error %v, want %v", stream, err, nil) } if reply.GetAggregatedPayloadSize() != sum { logger.Fatalf("%v.CloseAndRecv().GetAggregatePayloadSize() = %v; want %v", stream, reply.GetAggregatedPayloadSize(), sum) } } // DoServerStreaming performs a server streaming RPC. func DoServerStreaming(ctx context.Context, tc testpb.TestServiceClient, args ...grpc.CallOption) { respParam := make([]*testpb.ResponseParameters, len(respSizes)) for i, s := range respSizes { respParam[i] = &testpb.ResponseParameters{ Size: s, } } req := &testpb.StreamingOutputCallRequest{ ResponseType: testpb.PayloadType_COMPRESSABLE, ResponseParameters: respParam, } stream, err := tc.StreamingOutputCall(ctx, req, args...) if err != nil { logger.Fatalf("%v.StreamingOutputCall(_) = _, %v", tc, err) } var rpcStatus error var respCnt int var index int for { reply, err := stream.Recv() if err != nil { rpcStatus = err break } t := reply.GetPayload().GetType() if t != testpb.PayloadType_COMPRESSABLE { logger.Fatalf("Got the reply of type %d, want %d", t, testpb.PayloadType_COMPRESSABLE) } size := len(reply.GetPayload().GetBody()) if size != int(respSizes[index]) { logger.Fatalf("Got reply body of length %d, want %d", size, respSizes[index]) } index++ respCnt++ } if !errors.Is(rpcStatus, io.EOF) { logger.Fatalf("Failed to finish the server streaming rpc: %v", rpcStatus) } if respCnt != len(respSizes) { logger.Fatalf("Got %d reply, want %d", len(respSizes), respCnt) } } // DoPingPong performs ping-pong style bi-directional streaming RPC. func DoPingPong(ctx context.Context, tc testpb.TestServiceClient, args ...grpc.CallOption) { stream, err := tc.FullDuplexCall(ctx, args...) if err != nil { logger.Fatalf("%v.FullDuplexCall(_) = _, %v", tc, err) } var index int for index < len(reqSizes) { respParam := []*testpb.ResponseParameters{ { Size: respSizes[index], }, } pl := ClientNewPayload(testpb.PayloadType_COMPRESSABLE, int(reqSizes[index])) req := &testpb.StreamingOutputCallRequest{ ResponseType: testpb.PayloadType_COMPRESSABLE, ResponseParameters: respParam, Payload: pl, } if err := stream.Send(req); err != nil { logger.Fatalf("%v has error %v while sending %v", stream, err, req) } reply, err := stream.Recv() if err != nil { logger.Fatalf("%v.Recv() = %v", stream, err) } t := reply.GetPayload().GetType() if t != testpb.PayloadType_COMPRESSABLE { logger.Fatalf("Got the reply of type %d, want %d", t, testpb.PayloadType_COMPRESSABLE) } size := len(reply.GetPayload().GetBody()) if size != int(respSizes[index]) { logger.Fatalf("Got reply body of length %d, want %d", size, respSizes[index]) } index++ } if err := stream.CloseSend(); err != nil { logger.Fatalf("%v.CloseSend() got %v, want %v", stream, err, nil) } if _, err := stream.Recv(); !errors.Is(err, io.EOF) { logger.Fatalf("%v failed to complele the ping pong test: %v", stream, err) } } // DoEmptyStream sets up a bi-directional streaming with zero message. func DoEmptyStream(ctx context.Context, tc testpb.TestServiceClient, args ...grpc.CallOption) { stream, err := tc.FullDuplexCall(ctx, args...) if err != nil { logger.Fatalf("%v.FullDuplexCall(_) = _, %v", tc, err) } if err := stream.CloseSend(); err != nil { logger.Fatalf("%v.CloseSend() got %v, want %v", stream, err, nil) } if _, err := stream.Recv(); !errors.Is(err, io.EOF) { logger.Fatalf("%v failed to complete the empty stream test: %v", stream, err) } } type testServer struct { testpb.UnimplementedTestServiceServer } // NewTestServer creates a test server for test service. opts carries optional // settings and does not need to be provided. If multiple opts are provided, // only the first one is used. func NewTestServer() testpb.TestServiceServer { return &testServer{} } func (s *testServer) EmptyCall(ctx context.Context, in *testpb.Empty) (*testpb.Empty, error) { return new(testpb.Empty), nil } func serverNewPayload(t testpb.PayloadType, size int32) (*testpb.Payload, error) { if size < 0 { return nil, fmt.Errorf("requested a response with invalid length %d", size) } body := make([]byte, size) switch t { case testpb.PayloadType_COMPRESSABLE: default: return nil, fmt.Errorf("unsupported payload type: %d", t) } return &testpb.Payload{ Type: t, Body: body, }, nil } func (s *testServer) UnaryCall(ctx context.Context, in *testpb.SimpleRequest) (*testpb.SimpleResponse, error) { st := in.GetResponseStatus() if md, ok := metadata.FromIncomingContext(ctx); ok { if initialMetadata, ok := md[initialMetadataKey]; ok { header := metadata.Pairs(initialMetadataKey, initialMetadata[0]) _ = grpc.SendHeader(ctx, header) } if trailingMetadata, ok := md[trailingMetadataKey]; ok { trailer := metadata.Pairs(trailingMetadataKey, trailingMetadata[0]) _ = grpc.SetTrailer(ctx, trailer) } } if st != nil && st.Code != 0 { return nil, status.Error(codes.Code(st.Code), st.Message) // nolint:gosec // Status code can't be negative. } pl, err := serverNewPayload(in.GetResponseType(), in.GetResponseSize()) if err != nil { return nil, err } return &testpb.SimpleResponse{ Payload: pl, }, nil } func (s *testServer) StreamingOutputCall(args *testpb.StreamingOutputCallRequest, stream testpb.TestService_StreamingOutputCallServer) error { cs := args.GetResponseParameters() for _, c := range cs { if us := c.GetIntervalUs(); us > 0 { time.Sleep(time.Duration(us) * time.Microsecond) } pl, err := serverNewPayload(args.GetResponseType(), c.GetSize()) if err != nil { return err } if err := stream.Send(&testpb.StreamingOutputCallResponse{ Payload: pl, }); err != nil { return err } } return nil } func (s *testServer) StreamingInputCall(stream testpb.TestService_StreamingInputCallServer) error { var sum int32 for { in, err := stream.Recv() if errors.Is(err, io.EOF) { return stream.SendAndClose(&testpb.StreamingInputCallResponse{ AggregatedPayloadSize: sum, }) } if err != nil { return err } n := len(in.GetPayload().GetBody()) // This could overflow, but given this is a test and the negative value // should be detectable this should be good enough. sum += int32(n) // nolint: gosec } } func (s *testServer) FullDuplexCall(stream testpb.TestService_FullDuplexCallServer) error { if md, ok := metadata.FromIncomingContext(stream.Context()); ok { if initialMetadata, ok := md[initialMetadataKey]; ok { header := metadata.Pairs(initialMetadataKey, initialMetadata[0]) _ = stream.SendHeader(header) } if trailingMetadata, ok := md[trailingMetadataKey]; ok { trailer := metadata.Pairs(trailingMetadataKey, trailingMetadata[0]) stream.SetTrailer(trailer) } } for { in, err := stream.Recv() if errors.Is(err, io.EOF) { // read done. return nil } if err != nil { return err } st := in.GetResponseStatus() if st != nil && st.Code != 0 { return status.Error(codes.Code(st.Code), st.Message) // nolint:gosec // Status code can't be negative. } cs := in.GetResponseParameters() for _, c := range cs { if us := c.GetIntervalUs(); us > 0 { time.Sleep(time.Duration(us) * time.Microsecond) } pl, err := serverNewPayload(in.GetResponseType(), c.GetSize()) if err != nil { return err } if err := stream.Send(&testpb.StreamingOutputCallResponse{ Payload: pl, }); err != nil { return err } } } } func (s *testServer) HalfDuplexCall(stream testpb.TestService_HalfDuplexCallServer) error { var msgBuf []*testpb.StreamingOutputCallRequest for { in, err := stream.Recv() if errors.Is(err, io.EOF) { // read done. break } if err != nil { return err } msgBuf = append(msgBuf, in) } for _, m := range msgBuf { cs := m.GetResponseParameters() for _, c := range cs { if us := c.GetIntervalUs(); us > 0 { time.Sleep(time.Duration(us) * time.Microsecond) } pl, err := serverNewPayload(m.GetResponseType(), c.GetSize()) if err != nil { return err } if err := stream.Send(&testpb.StreamingOutputCallResponse{ Payload: pl, }); err != nil { return err } } } return nil } metadata_supplier.go000066400000000000000000000044351470323427300375570ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/google.golang.org/grpc/otelgrpc// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelgrpc // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" import ( "context" "google.golang.org/grpc/metadata" "go.opentelemetry.io/otel/baggage" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" ) type metadataSupplier struct { metadata *metadata.MD } // assert that metadataSupplier implements the TextMapCarrier interface. var _ propagation.TextMapCarrier = &metadataSupplier{} func (s *metadataSupplier) Get(key string) string { values := s.metadata.Get(key) if len(values) == 0 { return "" } return values[0] } func (s *metadataSupplier) Set(key string, value string) { s.metadata.Set(key, value) } func (s *metadataSupplier) Keys() []string { out := make([]string, 0, len(*s.metadata)) for key := range *s.metadata { out = append(out, key) } return out } // Inject injects correlation context and span context into the gRPC // metadata object. This function is meant to be used on outgoing // requests. // Deprecated: Unnecessary public func. func Inject(ctx context.Context, md *metadata.MD, opts ...Option) { c := newConfig(opts, "") c.Propagators.Inject(ctx, &metadataSupplier{ metadata: md, }) } func inject(ctx context.Context, propagators propagation.TextMapPropagator) context.Context { md, ok := metadata.FromOutgoingContext(ctx) if !ok { md = metadata.MD{} } propagators.Inject(ctx, &metadataSupplier{ metadata: &md, }) return metadata.NewOutgoingContext(ctx, md) } // Extract returns the correlation context and span context that // another service encoded in the gRPC metadata object with Inject. // This function is meant to be used on incoming requests. // Deprecated: Unnecessary public func. func Extract(ctx context.Context, md *metadata.MD, opts ...Option) (baggage.Baggage, trace.SpanContext) { c := newConfig(opts, "") ctx = c.Propagators.Extract(ctx, &metadataSupplier{ metadata: md, }) return baggage.FromContext(ctx), trace.SpanContextFromContext(ctx) } func extract(ctx context.Context, propagators propagation.TextMapPropagator) context.Context { md, ok := metadata.FromIncomingContext(ctx) if !ok { md = metadata.MD{} } return propagators.Extract(ctx, &metadataSupplier{ metadata: &md, }) } metadata_supplier_test.go000066400000000000000000000007501470323427300406120ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/google.golang.org/grpc/otelgrpc// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelgrpc import ( "testing" "github.com/stretchr/testify/assert" "google.golang.org/grpc/metadata" ) func TestMetadataSupplier(t *testing.T) { md := metadata.New(map[string]string{ "k1": "v1", }) ms := &metadataSupplier{&md} v1 := ms.Get("k1") assert.Equal(t, "v1", v1) ms.Set("k2", "v2") v1 = ms.Get("k1") v2 := ms.Get("k2") assert.Equal(t, "v1", v1) assert.Equal(t, "v2", v2) } semconv.go000066400000000000000000000025151470323427300355230ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/google.golang.org/grpc/otelgrpc// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelgrpc // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" import ( "go.opentelemetry.io/otel/attribute" semconv "go.opentelemetry.io/otel/semconv/v1.17.0" ) // Semantic conventions for attribute keys for gRPC. const ( // Name of message transmitted or received. RPCNameKey = attribute.Key("name") // Type of message transmitted or received. RPCMessageTypeKey = attribute.Key("message.type") // Identifier of message transmitted or received. RPCMessageIDKey = attribute.Key("message.id") // The compressed size of the message transmitted or received in bytes. RPCMessageCompressedSizeKey = attribute.Key("message.compressed_size") // The uncompressed size of the message transmitted or received in // bytes. RPCMessageUncompressedSizeKey = attribute.Key("message.uncompressed_size") ) // Semantic conventions for common RPC attributes. var ( // Semantic convention for gRPC as the remoting system. RPCSystemGRPC = semconv.RPCSystemGRPC // Semantic convention for a message named message. RPCNameMessage = RPCNameKey.String("message") // Semantic conventions for RPC message types. RPCMessageTypeSent = RPCMessageTypeKey.String("SENT") RPCMessageTypeReceived = RPCMessageTypeKey.String("RECEIVED") ) stats_handler.go000066400000000000000000000146021470323427300367040ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/google.golang.org/grpc/otelgrpc// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelgrpc // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" import ( "context" "sync/atomic" "time" grpc_codes "google.golang.org/grpc/codes" "google.golang.org/grpc/peer" "google.golang.org/grpc/stats" "google.golang.org/grpc/status" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/internal" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/metric" semconv "go.opentelemetry.io/otel/semconv/v1.17.0" "go.opentelemetry.io/otel/trace" ) type gRPCContextKey struct{} type gRPCContext struct { messagesReceived int64 messagesSent int64 metricAttrs []attribute.KeyValue record bool } type serverHandler struct { *config } // NewServerHandler creates a stats.Handler for a gRPC server. func NewServerHandler(opts ...Option) stats.Handler { h := &serverHandler{ config: newConfig(opts, "server"), } return h } // TagConn can attach some information to the given context. func (h *serverHandler) TagConn(ctx context.Context, info *stats.ConnTagInfo) context.Context { return ctx } // HandleConn processes the Conn stats. func (h *serverHandler) HandleConn(ctx context.Context, info stats.ConnStats) { } // TagRPC can attach some information to the given context. func (h *serverHandler) TagRPC(ctx context.Context, info *stats.RPCTagInfo) context.Context { ctx = extract(ctx, h.config.Propagators) name, attrs := internal.ParseFullMethod(info.FullMethodName) attrs = append(attrs, RPCSystemGRPC) ctx, _ = h.tracer.Start( trace.ContextWithRemoteSpanContext(ctx, trace.SpanContextFromContext(ctx)), name, trace.WithSpanKind(trace.SpanKindServer), trace.WithAttributes(append(attrs, h.config.SpanAttributes...)...), ) gctx := gRPCContext{ metricAttrs: append(attrs, h.config.MetricAttributes...), record: true, } if h.config.Filter != nil { gctx.record = h.config.Filter(info) } return context.WithValue(ctx, gRPCContextKey{}, &gctx) } // HandleRPC processes the RPC stats. func (h *serverHandler) HandleRPC(ctx context.Context, rs stats.RPCStats) { isServer := true h.handleRPC(ctx, rs, isServer) } type clientHandler struct { *config } // NewClientHandler creates a stats.Handler for a gRPC client. func NewClientHandler(opts ...Option) stats.Handler { h := &clientHandler{ config: newConfig(opts, "client"), } return h } // TagRPC can attach some information to the given context. func (h *clientHandler) TagRPC(ctx context.Context, info *stats.RPCTagInfo) context.Context { name, attrs := internal.ParseFullMethod(info.FullMethodName) attrs = append(attrs, RPCSystemGRPC) ctx, _ = h.tracer.Start( ctx, name, trace.WithSpanKind(trace.SpanKindClient), trace.WithAttributes(append(attrs, h.config.SpanAttributes...)...), ) gctx := gRPCContext{ metricAttrs: append(attrs, h.config.MetricAttributes...), record: true, } if h.config.Filter != nil { gctx.record = h.config.Filter(info) } return inject(context.WithValue(ctx, gRPCContextKey{}, &gctx), h.config.Propagators) } // HandleRPC processes the RPC stats. func (h *clientHandler) HandleRPC(ctx context.Context, rs stats.RPCStats) { isServer := false h.handleRPC(ctx, rs, isServer) } // TagConn can attach some information to the given context. func (h *clientHandler) TagConn(ctx context.Context, info *stats.ConnTagInfo) context.Context { return ctx } // HandleConn processes the Conn stats. func (h *clientHandler) HandleConn(context.Context, stats.ConnStats) { // no-op } func (c *config) handleRPC(ctx context.Context, rs stats.RPCStats, isServer bool) { // nolint: revive // isServer is not a control flag. span := trace.SpanFromContext(ctx) var metricAttrs []attribute.KeyValue var messageId int64 gctx, _ := ctx.Value(gRPCContextKey{}).(*gRPCContext) if gctx != nil { if !gctx.record { return } metricAttrs = make([]attribute.KeyValue, 0, len(gctx.metricAttrs)+1) metricAttrs = append(metricAttrs, gctx.metricAttrs...) } switch rs := rs.(type) { case *stats.Begin: case *stats.InPayload: if gctx != nil { messageId = atomic.AddInt64(&gctx.messagesReceived, 1) c.rpcRequestSize.Record(ctx, int64(rs.Length), metric.WithAttributeSet(attribute.NewSet(metricAttrs...))) } if c.ReceivedEvent { span.AddEvent("message", trace.WithAttributes( semconv.MessageTypeReceived, semconv.MessageIDKey.Int64(messageId), semconv.MessageCompressedSizeKey.Int(rs.CompressedLength), semconv.MessageUncompressedSizeKey.Int(rs.Length), ), ) } case *stats.OutPayload: if gctx != nil { messageId = atomic.AddInt64(&gctx.messagesSent, 1) c.rpcResponseSize.Record(ctx, int64(rs.Length), metric.WithAttributeSet(attribute.NewSet(metricAttrs...))) } if c.SentEvent { span.AddEvent("message", trace.WithAttributes( semconv.MessageTypeSent, semconv.MessageIDKey.Int64(messageId), semconv.MessageCompressedSizeKey.Int(rs.CompressedLength), semconv.MessageUncompressedSizeKey.Int(rs.Length), ), ) } case *stats.OutTrailer: case *stats.OutHeader: if p, ok := peer.FromContext(ctx); ok { span.SetAttributes(peerAttr(p.Addr.String())...) } case *stats.End: var rpcStatusAttr attribute.KeyValue if rs.Error != nil { s, _ := status.FromError(rs.Error) if isServer { statusCode, msg := serverStatus(s) span.SetStatus(statusCode, msg) } else { span.SetStatus(codes.Error, s.Message()) } rpcStatusAttr = semconv.RPCGRPCStatusCodeKey.Int(int(s.Code())) } else { rpcStatusAttr = semconv.RPCGRPCStatusCodeKey.Int(int(grpc_codes.OK)) } span.SetAttributes(rpcStatusAttr) span.End() metricAttrs = append(metricAttrs, rpcStatusAttr) // Allocate vararg slice once. recordOpts := []metric.RecordOption{metric.WithAttributeSet(attribute.NewSet(metricAttrs...))} // Use floating point division here for higher precision (instead of Millisecond method). // Measure right before calling Record() to capture as much elapsed time as possible. elapsedTime := float64(rs.EndTime.Sub(rs.BeginTime)) / float64(time.Millisecond) c.rpcDuration.Record(ctx, elapsedTime, recordOpts...) if gctx != nil { c.rpcRequestsPerRPC.Record(ctx, atomic.LoadInt64(&gctx.messagesReceived), recordOpts...) c.rpcResponsesPerRPC.Record(ctx, atomic.LoadInt64(&gctx.messagesSent), recordOpts...) } default: return } } test/000077500000000000000000000000001470323427300344765ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/google.golang.org/grpc/otelgrpcdoc.go000066400000000000000000000006701470323427300355750ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/google.golang.org/grpc/otelgrpc/test// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 /* Package test validates the otelgrpc instrumentation with the default SDK. This package is in a separate module from the instrumentation it tests to isolate the dependency of the default SDK and not impose this as a transitive dependency for users. */ package test // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/test" go.mod000066400000000000000000000022121470323427300356010ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/google.golang.org/grpc/otelgrpc/testmodule go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/test go 1.22 require ( github.com/stretchr/testify v1.9.0 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.56.0 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/sdk v1.31.0 go.opentelemetry.io/otel/sdk/metric v1.31.0 go.opentelemetry.io/otel/trace v1.31.0 go.uber.org/goleak v1.3.0 google.golang.org/grpc v1.67.1 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/kr/text v0.2.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect golang.org/x/net v0.30.0 // indirect golang.org/x/sys v0.26.0 // indirect golang.org/x/text v0.19.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 // indirect google.golang.org/protobuf v1.35.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) replace go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc => ../ go.sum000066400000000000000000000102251470323427300356310ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/google.golang.org/grpc/otelgrpc/testgithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc= go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 h1:QCqS/PdaHTSWGvupk2F/ehwHtGc0/GYkT+3GAcR1CCc= google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= grpc_stats_handler_test.go000066400000000000000000001517771470323427300417540ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/google.golang.org/grpc/otelgrpc/test// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package test import ( "context" "errors" "io" "net" "strconv" "sync" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/filters" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/internal/test" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/instrumentation" "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest" "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/sdk/trace/tracetest" semconv "go.opentelemetry.io/otel/semconv/v1.17.0" testpb "google.golang.org/grpc/interop/grpc_testing" ) var ( testSpanAttr = attribute.String("test_span", "OK") testMetricAttr = attribute.String("test_metric", "OK") ) func TestStatsHandler(t *testing.T) { tests := []struct { name string filterSvcName string expectRecorded bool }{ { name: "Recorded", filterSvcName: "grpc.testing.TestService", expectRecorded: true, }, { name: "Dropped", filterSvcName: "grpc.testing.OtherService", expectRecorded: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Setenv("OTEL_METRICS_EXEMPLAR_FILTER", "always_off") clientSR := tracetest.NewSpanRecorder() clientTP := trace.NewTracerProvider(trace.WithSpanProcessor(clientSR)) clientMetricReader := metric.NewManualReader() clientMP := metric.NewMeterProvider(metric.WithReader(clientMetricReader)) serverSR := tracetest.NewSpanRecorder() serverTP := trace.NewTracerProvider(trace.WithSpanProcessor(serverSR)) serverMetricReader := metric.NewManualReader() serverMP := metric.NewMeterProvider(metric.WithReader(serverMetricReader)) listener, err := net.Listen("tcp", "127.0.0.1:0") require.NoError(t, err, "failed to open port") client := newGrpcTest(t, listener, []grpc.DialOption{ grpc.WithStatsHandler(otelgrpc.NewClientHandler( otelgrpc.WithTracerProvider(clientTP), otelgrpc.WithMeterProvider(clientMP), otelgrpc.WithMessageEvents(otelgrpc.ReceivedEvents, otelgrpc.SentEvents), otelgrpc.WithFilter(filters.ServiceName(tt.filterSvcName)), otelgrpc.WithSpanAttributes(testSpanAttr), otelgrpc.WithMetricAttributes(testMetricAttr)), ), }, []grpc.ServerOption{ grpc.StatsHandler(otelgrpc.NewServerHandler( otelgrpc.WithTracerProvider(serverTP), otelgrpc.WithMeterProvider(serverMP), otelgrpc.WithMessageEvents(otelgrpc.ReceivedEvents, otelgrpc.SentEvents), otelgrpc.WithFilter(filters.ServiceName(tt.filterSvcName)), otelgrpc.WithSpanAttributes(testSpanAttr), otelgrpc.WithMetricAttributes(testMetricAttr)), ), }, ) ctx, cancel := context.WithCancel(context.Background()) defer cancel() doCalls(ctx, client) if tt.expectRecorded { t.Run("ClientSpans", func(t *testing.T) { checkClientSpans(t, clientSR.Ended(), listener.Addr().String()) }) t.Run("ClientMetrics", func(t *testing.T) { checkClientMetrics(t, clientMetricReader) }) t.Run("ServerSpans", func(t *testing.T) { checkServerSpans(t, serverSR.Ended()) }) t.Run("ServerMetrics", func(t *testing.T) { checkServerMetrics(t, serverMetricReader) }) } else { t.Run("ClientSpans", func(t *testing.T) { require.Empty(t, clientSR.Ended()) }) t.Run("ClientMetrics", func(t *testing.T) { rm := metricdata.ResourceMetrics{} err := clientMetricReader.Collect(context.Background(), &rm) assert.NoError(t, err) require.Empty(t, rm.ScopeMetrics) }) t.Run("ServerSpans", func(t *testing.T) { require.Empty(t, serverSR.Ended()) }) t.Run("ServerMetrics", func(t *testing.T) { rm := metricdata.ResourceMetrics{} err := serverMetricReader.Collect(context.Background(), &rm) assert.NoError(t, err) require.Empty(t, rm.ScopeMetrics) }) } }) } } func checkClientSpans(t *testing.T, spans []trace.ReadOnlySpan, addr string) { require.Len(t, spans, 5) host, p, err := net.SplitHostPort(addr) require.NoError(t, err) port, err := strconv.Atoi(p) require.NoError(t, err) emptySpan := spans[0] assert.False(t, emptySpan.EndTime().IsZero()) assert.Equal(t, "grpc.testing.TestService/EmptyCall", emptySpan.Name()) assertEvents(t, []trace.Event{ { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(1), otelgrpc.RPCMessageTypeKey.String("SENT"), otelgrpc.RPCMessageCompressedSizeKey.Int(0), otelgrpc.RPCMessageUncompressedSizeKey.Int(0), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(1), otelgrpc.RPCMessageTypeKey.String("RECEIVED"), otelgrpc.RPCMessageCompressedSizeKey.Int(0), otelgrpc.RPCMessageUncompressedSizeKey.Int(0), }, }, }, emptySpan.Events()) assert.ElementsMatch(t, []attribute.KeyValue{ semconv.RPCMethodKey.String("EmptyCall"), semconv.RPCServiceKey.String("grpc.testing.TestService"), otelgrpc.RPCSystemGRPC, otelgrpc.GRPCStatusCodeKey.Int64(int64(codes.OK)), semconv.NetSockPeerAddr(host), semconv.NetSockPeerPort(port), testSpanAttr, }, emptySpan.Attributes()) largeSpan := spans[1] assert.False(t, largeSpan.EndTime().IsZero()) assert.Equal(t, "grpc.testing.TestService/UnaryCall", largeSpan.Name()) assertEvents(t, []trace.Event{ { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(1), otelgrpc.RPCMessageTypeKey.String("SENT"), otelgrpc.RPCMessageCompressedSizeKey.Int(271840), otelgrpc.RPCMessageUncompressedSizeKey.Int(271840), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(1), otelgrpc.RPCMessageTypeKey.String("RECEIVED"), otelgrpc.RPCMessageCompressedSizeKey.Int(314167), otelgrpc.RPCMessageUncompressedSizeKey.Int(314167), }, }, }, largeSpan.Events()) assert.ElementsMatch(t, []attribute.KeyValue{ semconv.RPCMethodKey.String("UnaryCall"), semconv.RPCServiceKey.String("grpc.testing.TestService"), otelgrpc.RPCSystemGRPC, otelgrpc.GRPCStatusCodeKey.Int64(int64(codes.OK)), semconv.NetSockPeerAddr(host), semconv.NetSockPeerPort(port), testSpanAttr, }, largeSpan.Attributes()) streamInput := spans[2] assert.False(t, streamInput.EndTime().IsZero()) assert.Equal(t, "grpc.testing.TestService/StreamingInputCall", streamInput.Name()) assertEvents(t, []trace.Event{ { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(1), otelgrpc.RPCMessageTypeKey.String("SENT"), otelgrpc.RPCMessageCompressedSizeKey.Int(27190), otelgrpc.RPCMessageUncompressedSizeKey.Int(27190), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(2), otelgrpc.RPCMessageTypeKey.String("SENT"), otelgrpc.RPCMessageCompressedSizeKey.Int(12), otelgrpc.RPCMessageUncompressedSizeKey.Int(12), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(3), otelgrpc.RPCMessageTypeKey.String("SENT"), otelgrpc.RPCMessageCompressedSizeKey.Int(1834), otelgrpc.RPCMessageUncompressedSizeKey.Int(1834), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(4), otelgrpc.RPCMessageTypeKey.String("SENT"), otelgrpc.RPCMessageCompressedSizeKey.Int(45912), otelgrpc.RPCMessageUncompressedSizeKey.Int(45912), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(1), otelgrpc.RPCMessageTypeKey.String("RECEIVED"), otelgrpc.RPCMessageCompressedSizeKey.Int(4), otelgrpc.RPCMessageUncompressedSizeKey.Int(4), }, }, // client does not record an event for the server response. }, streamInput.Events()) assert.ElementsMatch(t, []attribute.KeyValue{ semconv.RPCMethodKey.String("StreamingInputCall"), semconv.RPCServiceKey.String("grpc.testing.TestService"), otelgrpc.RPCSystemGRPC, otelgrpc.GRPCStatusCodeKey.Int64(int64(codes.OK)), semconv.NetSockPeerAddr(host), semconv.NetSockPeerPort(port), testSpanAttr, }, streamInput.Attributes()) streamOutput := spans[3] assert.False(t, streamOutput.EndTime().IsZero()) assert.Equal(t, "grpc.testing.TestService/StreamingOutputCall", streamOutput.Name()) assertEvents(t, []trace.Event{ { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(1), otelgrpc.RPCMessageTypeKey.String("SENT"), otelgrpc.RPCMessageCompressedSizeKey.Int(21), otelgrpc.RPCMessageUncompressedSizeKey.Int(21), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(1), otelgrpc.RPCMessageTypeKey.String("RECEIVED"), otelgrpc.RPCMessageCompressedSizeKey.Int(31423), otelgrpc.RPCMessageUncompressedSizeKey.Int(31423), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(2), otelgrpc.RPCMessageTypeKey.String("RECEIVED"), otelgrpc.RPCMessageCompressedSizeKey.Int(13), otelgrpc.RPCMessageUncompressedSizeKey.Int(13), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(3), otelgrpc.RPCMessageTypeKey.String("RECEIVED"), otelgrpc.RPCMessageCompressedSizeKey.Int(2659), otelgrpc.RPCMessageUncompressedSizeKey.Int(2659), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(4), otelgrpc.RPCMessageTypeKey.String("RECEIVED"), otelgrpc.RPCMessageCompressedSizeKey.Int(58987), otelgrpc.RPCMessageUncompressedSizeKey.Int(58987), }, }, }, streamOutput.Events()) assert.ElementsMatch(t, []attribute.KeyValue{ semconv.RPCMethodKey.String("StreamingOutputCall"), semconv.RPCServiceKey.String("grpc.testing.TestService"), otelgrpc.RPCSystemGRPC, otelgrpc.GRPCStatusCodeKey.Int64(int64(codes.OK)), semconv.NetSockPeerAddr(host), semconv.NetSockPeerPort(port), testSpanAttr, }, streamOutput.Attributes()) pingPong := spans[4] assert.False(t, pingPong.EndTime().IsZero()) assert.Equal(t, "grpc.testing.TestService/FullDuplexCall", pingPong.Name()) assertEvents(t, []trace.Event{ { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(1), otelgrpc.RPCMessageTypeKey.String("SENT"), otelgrpc.RPCMessageCompressedSizeKey.Int(27196), otelgrpc.RPCMessageUncompressedSizeKey.Int(27196), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(1), otelgrpc.RPCMessageTypeKey.String("RECEIVED"), otelgrpc.RPCMessageCompressedSizeKey.Int(31423), otelgrpc.RPCMessageUncompressedSizeKey.Int(31423), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(2), otelgrpc.RPCMessageTypeKey.String("SENT"), otelgrpc.RPCMessageCompressedSizeKey.Int(16), otelgrpc.RPCMessageUncompressedSizeKey.Int(16), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(2), otelgrpc.RPCMessageTypeKey.String("RECEIVED"), otelgrpc.RPCMessageCompressedSizeKey.Int(13), otelgrpc.RPCMessageUncompressedSizeKey.Int(13), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(3), otelgrpc.RPCMessageTypeKey.String("SENT"), otelgrpc.RPCMessageCompressedSizeKey.Int(1839), otelgrpc.RPCMessageUncompressedSizeKey.Int(1839), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(3), otelgrpc.RPCMessageTypeKey.String("RECEIVED"), otelgrpc.RPCMessageCompressedSizeKey.Int(2659), otelgrpc.RPCMessageUncompressedSizeKey.Int(2659), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(4), otelgrpc.RPCMessageTypeKey.String("SENT"), otelgrpc.RPCMessageCompressedSizeKey.Int(45918), otelgrpc.RPCMessageUncompressedSizeKey.Int(45918), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(4), otelgrpc.RPCMessageTypeKey.String("RECEIVED"), otelgrpc.RPCMessageCompressedSizeKey.Int(58987), otelgrpc.RPCMessageUncompressedSizeKey.Int(58987), }, }, }, pingPong.Events()) assert.ElementsMatch(t, []attribute.KeyValue{ semconv.RPCMethodKey.String("FullDuplexCall"), semconv.RPCServiceKey.String("grpc.testing.TestService"), otelgrpc.RPCSystemGRPC, otelgrpc.GRPCStatusCodeKey.Int64(int64(codes.OK)), semconv.NetSockPeerAddr(host), semconv.NetSockPeerPort(port), testSpanAttr, }, pingPong.Attributes()) } func checkServerSpans(t *testing.T, spans []trace.ReadOnlySpan) { require.Len(t, spans, 5) emptySpan := spans[0] assert.False(t, emptySpan.EndTime().IsZero()) assert.Equal(t, "grpc.testing.TestService/EmptyCall", emptySpan.Name()) assertEvents(t, []trace.Event{ { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(1), otelgrpc.RPCMessageTypeKey.String("RECEIVED"), otelgrpc.RPCMessageCompressedSizeKey.Int(0), otelgrpc.RPCMessageUncompressedSizeKey.Int(0), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(1), otelgrpc.RPCMessageTypeKey.String("SENT"), otelgrpc.RPCMessageCompressedSizeKey.Int(0), otelgrpc.RPCMessageUncompressedSizeKey.Int(0), }, }, }, emptySpan.Events()) port, ok := findAttribute(emptySpan.Attributes(), semconv.NetSockPeerPortKey) assert.True(t, ok) assert.ElementsMatch(t, []attribute.KeyValue{ semconv.RPCMethodKey.String("EmptyCall"), semconv.RPCServiceKey.String("grpc.testing.TestService"), otelgrpc.RPCSystemGRPC, otelgrpc.GRPCStatusCodeKey.Int64(int64(codes.OK)), semconv.NetSockPeerAddr("127.0.0.1"), port, testSpanAttr, }, emptySpan.Attributes()) largeSpan := spans[1] assert.False(t, largeSpan.EndTime().IsZero()) assert.Equal(t, "grpc.testing.TestService/UnaryCall", largeSpan.Name()) assertEvents(t, []trace.Event{ { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageTypeKey.String("RECEIVED"), otelgrpc.RPCMessageIDKey.Int(1), otelgrpc.RPCMessageCompressedSizeKey.Int(271840), otelgrpc.RPCMessageUncompressedSizeKey.Int(271840), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageTypeKey.String("SENT"), otelgrpc.RPCMessageIDKey.Int(1), otelgrpc.RPCMessageCompressedSizeKey.Int(314167), otelgrpc.RPCMessageUncompressedSizeKey.Int(314167), }, }, }, largeSpan.Events()) port, ok = findAttribute(largeSpan.Attributes(), semconv.NetSockPeerPortKey) assert.True(t, ok) assert.ElementsMatch(t, []attribute.KeyValue{ semconv.RPCMethodKey.String("UnaryCall"), semconv.RPCServiceKey.String("grpc.testing.TestService"), otelgrpc.RPCSystemGRPC, otelgrpc.GRPCStatusCodeKey.Int64(int64(codes.OK)), semconv.NetSockPeerAddr("127.0.0.1"), port, testSpanAttr, }, largeSpan.Attributes()) streamInput := spans[2] assert.False(t, streamInput.EndTime().IsZero()) assert.Equal(t, "grpc.testing.TestService/StreamingInputCall", streamInput.Name()) assertEvents(t, []trace.Event{ { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(1), otelgrpc.RPCMessageTypeKey.String("RECEIVED"), otelgrpc.RPCMessageCompressedSizeKey.Int(27190), otelgrpc.RPCMessageUncompressedSizeKey.Int(27190), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(2), otelgrpc.RPCMessageTypeKey.String("RECEIVED"), otelgrpc.RPCMessageCompressedSizeKey.Int(12), otelgrpc.RPCMessageUncompressedSizeKey.Int(12), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(3), otelgrpc.RPCMessageTypeKey.String("RECEIVED"), otelgrpc.RPCMessageCompressedSizeKey.Int(1834), otelgrpc.RPCMessageUncompressedSizeKey.Int(1834), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(4), otelgrpc.RPCMessageTypeKey.String("RECEIVED"), otelgrpc.RPCMessageCompressedSizeKey.Int(45912), otelgrpc.RPCMessageUncompressedSizeKey.Int(45912), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(1), otelgrpc.RPCMessageTypeKey.String("SENT"), otelgrpc.RPCMessageCompressedSizeKey.Int(4), otelgrpc.RPCMessageUncompressedSizeKey.Int(4), }, }, // client does not record an event for the server response. }, streamInput.Events()) port, ok = findAttribute(streamInput.Attributes(), semconv.NetSockPeerPortKey) assert.True(t, ok) assert.ElementsMatch(t, []attribute.KeyValue{ semconv.RPCMethodKey.String("StreamingInputCall"), semconv.RPCServiceKey.String("grpc.testing.TestService"), otelgrpc.RPCSystemGRPC, otelgrpc.GRPCStatusCodeKey.Int64(int64(codes.OK)), semconv.NetSockPeerAddr("127.0.0.1"), port, testSpanAttr, }, streamInput.Attributes()) streamOutput := spans[3] assert.False(t, streamOutput.EndTime().IsZero()) assert.Equal(t, "grpc.testing.TestService/StreamingOutputCall", streamOutput.Name()) assertEvents(t, []trace.Event{ { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(1), otelgrpc.RPCMessageTypeKey.String("RECEIVED"), otelgrpc.RPCMessageCompressedSizeKey.Int(21), otelgrpc.RPCMessageUncompressedSizeKey.Int(21), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(1), otelgrpc.RPCMessageTypeKey.String("SENT"), otelgrpc.RPCMessageCompressedSizeKey.Int(31423), otelgrpc.RPCMessageUncompressedSizeKey.Int(31423), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(2), otelgrpc.RPCMessageTypeKey.String("SENT"), otelgrpc.RPCMessageCompressedSizeKey.Int(13), otelgrpc.RPCMessageUncompressedSizeKey.Int(13), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(3), otelgrpc.RPCMessageTypeKey.String("SENT"), otelgrpc.RPCMessageCompressedSizeKey.Int(2659), otelgrpc.RPCMessageUncompressedSizeKey.Int(2659), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(4), otelgrpc.RPCMessageTypeKey.String("SENT"), otelgrpc.RPCMessageCompressedSizeKey.Int(58987), otelgrpc.RPCMessageUncompressedSizeKey.Int(58987), }, }, }, streamOutput.Events()) port, ok = findAttribute(streamOutput.Attributes(), semconv.NetSockPeerPortKey) assert.True(t, ok) assert.ElementsMatch(t, []attribute.KeyValue{ semconv.RPCMethodKey.String("StreamingOutputCall"), semconv.RPCServiceKey.String("grpc.testing.TestService"), otelgrpc.RPCSystemGRPC, otelgrpc.GRPCStatusCodeKey.Int64(int64(codes.OK)), semconv.NetSockPeerAddr("127.0.0.1"), port, testSpanAttr, }, streamOutput.Attributes()) pingPong := spans[4] assert.False(t, pingPong.EndTime().IsZero()) assert.Equal(t, "grpc.testing.TestService/FullDuplexCall", pingPong.Name()) assertEvents(t, []trace.Event{ { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(1), otelgrpc.RPCMessageTypeKey.String("RECEIVED"), otelgrpc.RPCMessageCompressedSizeKey.Int(27196), otelgrpc.RPCMessageUncompressedSizeKey.Int(27196), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(1), otelgrpc.RPCMessageTypeKey.String("SENT"), otelgrpc.RPCMessageCompressedSizeKey.Int(31423), otelgrpc.RPCMessageUncompressedSizeKey.Int(31423), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(2), otelgrpc.RPCMessageTypeKey.String("RECEIVED"), otelgrpc.RPCMessageCompressedSizeKey.Int(16), otelgrpc.RPCMessageUncompressedSizeKey.Int(16), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(2), otelgrpc.RPCMessageTypeKey.String("SENT"), otelgrpc.RPCMessageCompressedSizeKey.Int(13), otelgrpc.RPCMessageUncompressedSizeKey.Int(13), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(3), otelgrpc.RPCMessageTypeKey.String("RECEIVED"), otelgrpc.RPCMessageCompressedSizeKey.Int(1839), otelgrpc.RPCMessageUncompressedSizeKey.Int(1839), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(3), otelgrpc.RPCMessageTypeKey.String("SENT"), otelgrpc.RPCMessageCompressedSizeKey.Int(2659), otelgrpc.RPCMessageUncompressedSizeKey.Int(2659), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(4), otelgrpc.RPCMessageTypeKey.String("RECEIVED"), otelgrpc.RPCMessageCompressedSizeKey.Int(45918), otelgrpc.RPCMessageUncompressedSizeKey.Int(45918), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(4), otelgrpc.RPCMessageTypeKey.String("SENT"), otelgrpc.RPCMessageCompressedSizeKey.Int(58987), otelgrpc.RPCMessageUncompressedSizeKey.Int(58987), }, }, }, pingPong.Events()) port, ok = findAttribute(pingPong.Attributes(), semconv.NetSockPeerPortKey) assert.True(t, ok) assert.ElementsMatch(t, []attribute.KeyValue{ semconv.RPCMethodKey.String("FullDuplexCall"), semconv.RPCServiceKey.String("grpc.testing.TestService"), otelgrpc.RPCSystemGRPC, otelgrpc.GRPCStatusCodeKey.Int64(int64(codes.OK)), semconv.NetSockPeerAddr("127.0.0.1"), port, testSpanAttr, }, pingPong.Attributes()) } func checkClientMetrics(t *testing.T, reader metric.Reader) { rm := metricdata.ResourceMetrics{} err := reader.Collect(context.Background(), &rm) assert.NoError(t, err) require.Len(t, rm.ScopeMetrics, 1) require.Len(t, rm.ScopeMetrics[0].Metrics, 5) expectedScopeMetric := metricdata.ScopeMetrics{ Scope: instrumentation.Scope{ Name: "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc", Version: otelgrpc.Version(), SchemaURL: "https://opentelemetry.io/schemas/1.17.0", }, Metrics: []metricdata.Metrics{ { Name: "rpc.client.duration", Description: "Measures the duration of inbound RPC.", Unit: "ms", Data: metricdata.Histogram[float64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[float64]{ { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("EmptyCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), }, { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("UnaryCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), }, { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("StreamingInputCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), }, { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("StreamingOutputCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), }, { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("FullDuplexCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), }, }, }, }, { Name: "rpc.client.request.size", Description: "Measures size of RPC request messages (uncompressed).", Unit: "By", Data: metricdata.Histogram[int64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[int64]{ { Attributes: attribute.NewSet( semconv.RPCMethod("EmptyCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(0)), Min: metricdata.NewExtrema(int64(0)), Count: 1, Sum: 0, }, { Attributes: attribute.NewSet( semconv.RPCMethod("UnaryCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, Max: metricdata.NewExtrema(int64(314167)), Min: metricdata.NewExtrema(int64(314167)), Count: 1, Sum: 314167, }, { Attributes: attribute.NewSet( semconv.RPCMethod("StreamingInputCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(4)), Min: metricdata.NewExtrema(int64(4)), Count: 1, Sum: 4, }, { Attributes: attribute.NewSet( semconv.RPCMethod("StreamingOutputCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2}, Max: metricdata.NewExtrema(int64(58987)), Min: metricdata.NewExtrema(int64(13)), Count: 4, Sum: 93082, }, { Attributes: attribute.NewSet( semconv.RPCMethod("FullDuplexCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2}, Max: metricdata.NewExtrema(int64(58987)), Min: metricdata.NewExtrema(int64(13)), Count: 4, Sum: 93082, }, }, }, }, { Name: "rpc.client.response.size", Description: "Measures size of RPC response messages (uncompressed).", Unit: "By", Data: metricdata.Histogram[int64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[int64]{ { Attributes: attribute.NewSet( semconv.RPCMethod("EmptyCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(0)), Min: metricdata.NewExtrema(int64(0)), Count: 1, Sum: 0, }, { Attributes: attribute.NewSet( semconv.RPCMethod("UnaryCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, Max: metricdata.NewExtrema(int64(271840)), Min: metricdata.NewExtrema(int64(271840)), Count: 1, Sum: 271840, }, { Attributes: attribute.NewSet( semconv.RPCMethod("StreamingInputCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2}, Max: metricdata.NewExtrema(int64(45912)), Min: metricdata.NewExtrema(int64(12)), Count: 4, Sum: 74948, }, { Attributes: attribute.NewSet( semconv.RPCMethod("StreamingOutputCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(21)), Min: metricdata.NewExtrema(int64(21)), Count: 1, Sum: 21, }, { Attributes: attribute.NewSet( semconv.RPCMethod("FullDuplexCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2}, Max: metricdata.NewExtrema(int64(45918)), Min: metricdata.NewExtrema(int64(16)), Count: 4, Sum: 74969, }, }, }, }, { Name: "rpc.client.requests_per_rpc", Description: "Measures the number of messages received per RPC. Should be 1 for all non-streaming RPCs.", Unit: "{count}", Data: metricdata.Histogram[int64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[int64]{ { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("EmptyCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(1)), Min: metricdata.NewExtrema(int64(1)), Count: 1, Sum: 1, }, { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("UnaryCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(1)), Min: metricdata.NewExtrema(int64(1)), Count: 1, Sum: 1, }, { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("StreamingInputCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(1)), Min: metricdata.NewExtrema(int64(1)), Count: 1, Sum: 1, }, { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("StreamingOutputCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(4)), Min: metricdata.NewExtrema(int64(4)), Count: 1, Sum: 4, }, { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("FullDuplexCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(4)), Min: metricdata.NewExtrema(int64(4)), Count: 1, Sum: 4, }, }, }, }, { Name: "rpc.client.responses_per_rpc", Description: "Measures the number of messages received per RPC. Should be 1 for all non-streaming RPCs.", Unit: "{count}", Data: metricdata.Histogram[int64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[int64]{ { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("EmptyCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(1)), Min: metricdata.NewExtrema(int64(1)), Count: 1, Sum: 1, }, { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("UnaryCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(1)), Min: metricdata.NewExtrema(int64(1)), Count: 1, Sum: 1, }, { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("StreamingInputCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(4)), Min: metricdata.NewExtrema(int64(4)), Count: 1, Sum: 4, }, { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("StreamingOutputCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(1)), Min: metricdata.NewExtrema(int64(1)), Count: 1, Sum: 1, }, { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("FullDuplexCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(4)), Min: metricdata.NewExtrema(int64(4)), Count: 1, Sum: 4, }, }, }, }, }, } metricdatatest.AssertEqual(t, expectedScopeMetric, rm.ScopeMetrics[0], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue()) } func checkServerMetrics(t *testing.T, reader metric.Reader) { rm := metricdata.ResourceMetrics{} err := reader.Collect(context.Background(), &rm) assert.NoError(t, err) require.Len(t, rm.ScopeMetrics, 1) require.Len(t, rm.ScopeMetrics[0].Metrics, 5) expectedScopeMetric := metricdata.ScopeMetrics{ Scope: instrumentation.Scope{ Name: "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc", Version: otelgrpc.Version(), SchemaURL: "https://opentelemetry.io/schemas/1.17.0", }, Metrics: []metricdata.Metrics{ { Name: "rpc.server.duration", Description: "Measures the duration of inbound RPC.", Unit: "ms", Data: metricdata.Histogram[float64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[float64]{ { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("EmptyCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), }, { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("UnaryCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), }, { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("StreamingInputCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), }, { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("StreamingOutputCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), }, { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("FullDuplexCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), }, }, }, }, { Name: "rpc.server.request.size", Description: "Measures size of RPC request messages (uncompressed).", Unit: "By", Data: metricdata.Histogram[int64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[int64]{ { Attributes: attribute.NewSet( semconv.RPCMethod("EmptyCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(0)), Min: metricdata.NewExtrema(int64(0)), Count: 1, Sum: 0, }, { Attributes: attribute.NewSet( semconv.RPCMethod("UnaryCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, Max: metricdata.NewExtrema(int64(271840)), Min: metricdata.NewExtrema(int64(271840)), Count: 1, Sum: 271840, }, { Attributes: attribute.NewSet( semconv.RPCMethod("StreamingInputCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2}, Max: metricdata.NewExtrema(int64(45912)), Min: metricdata.NewExtrema(int64(12)), Count: 4, Sum: 74948, }, { Attributes: attribute.NewSet( semconv.RPCMethod("StreamingOutputCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(21)), Min: metricdata.NewExtrema(int64(21)), Count: 1, Sum: 21, }, { Attributes: attribute.NewSet( semconv.RPCMethod("FullDuplexCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2}, Max: metricdata.NewExtrema(int64(45918)), Min: metricdata.NewExtrema(int64(16)), Count: 4, Sum: 74969, }, }, }, }, { Name: "rpc.server.response.size", Description: "Measures size of RPC response messages (uncompressed).", Unit: "By", Data: metricdata.Histogram[int64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[int64]{ { Attributes: attribute.NewSet( semconv.RPCMethod("EmptyCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(0)), Min: metricdata.NewExtrema(int64(0)), Count: 1, Sum: 0, }, { Attributes: attribute.NewSet( semconv.RPCMethod("UnaryCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, Max: metricdata.NewExtrema(int64(314167)), Min: metricdata.NewExtrema(int64(314167)), Count: 1, Sum: 314167, }, { Attributes: attribute.NewSet( semconv.RPCMethod("StreamingInputCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(4)), Min: metricdata.NewExtrema(int64(4)), Count: 1, Sum: 4, }, { Attributes: attribute.NewSet( semconv.RPCMethod("StreamingOutputCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2}, Max: metricdata.NewExtrema(int64(58987)), Min: metricdata.NewExtrema(int64(13)), Count: 4, Sum: 93082, }, { Attributes: attribute.NewSet( semconv.RPCMethod("FullDuplexCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2}, Max: metricdata.NewExtrema(int64(58987)), Min: metricdata.NewExtrema(int64(13)), Count: 4, Sum: 93082, }, }, }, }, { Name: "rpc.server.requests_per_rpc", Description: "Measures the number of messages received per RPC. Should be 1 for all non-streaming RPCs.", Unit: "{count}", Data: metricdata.Histogram[int64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[int64]{ { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("EmptyCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(1)), Min: metricdata.NewExtrema(int64(1)), Count: 1, Sum: 1, }, { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("UnaryCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(1)), Min: metricdata.NewExtrema(int64(1)), Count: 1, Sum: 1, }, { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("StreamingInputCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(4)), Min: metricdata.NewExtrema(int64(4)), Count: 1, Sum: 4, }, { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("StreamingOutputCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(1)), Min: metricdata.NewExtrema(int64(1)), Count: 1, Sum: 1, }, { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("FullDuplexCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(4)), Min: metricdata.NewExtrema(int64(4)), Count: 1, Sum: 4, }, }, }, }, { Name: "rpc.server.responses_per_rpc", Description: "Measures the number of messages received per RPC. Should be 1 for all non-streaming RPCs.", Unit: "{count}", Data: metricdata.Histogram[int64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[int64]{ { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("EmptyCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(1)), Min: metricdata.NewExtrema(int64(1)), Count: 1, Sum: 1, }, { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("UnaryCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(1)), Min: metricdata.NewExtrema(int64(1)), Count: 1, Sum: 1, }, { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("StreamingInputCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(1)), Min: metricdata.NewExtrema(int64(1)), Count: 1, Sum: 1, }, { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("StreamingOutputCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(4)), Min: metricdata.NewExtrema(int64(4)), Count: 1, Sum: 4, }, { Attributes: attribute.NewSet( semconv.RPCGRPCStatusCodeOk, semconv.RPCMethod("FullDuplexCall"), semconv.RPCService("grpc.testing.TestService"), semconv.RPCSystemGRPC, testMetricAttr), Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, BucketCounts: []uint64{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Max: metricdata.NewExtrema(int64(4)), Min: metricdata.NewExtrema(int64(4)), Count: 1, Sum: 4, }, }, }, }, }, } metricdatatest.AssertEqual(t, expectedScopeMetric, rm.ScopeMetrics[0], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue()) } // Ensure there is no data race for the following scenario: // Bidirectional streaming + client cancels context in the middle of streaming. func TestStatsHandlerConcurrentSafeContextCancellation(t *testing.T) { listener, err := net.Listen("tcp", "127.0.0.1:0") require.NoError(t, err, "failed to open port") client := newGrpcTest(t, listener, []grpc.DialOption{ grpc.WithStatsHandler(otelgrpc.NewClientHandler()), }, []grpc.ServerOption{ grpc.StatsHandler(otelgrpc.NewServerHandler()), }, ) const n = 10 for i := 0; i < n; i++ { ctx, cancel := context.WithCancel(context.Background()) stream, err := client.FullDuplexCall(ctx) require.NoError(t, err) const messageCount = 10 var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() for i := 0; i < messageCount; i++ { const reqSize = 1 pl := test.ClientNewPayload(testpb.PayloadType_COMPRESSABLE, reqSize) respParam := []*testpb.ResponseParameters{ { Size: reqSize, }, } req := &testpb.StreamingOutputCallRequest{ ResponseType: testpb.PayloadType_COMPRESSABLE, ResponseParameters: respParam, Payload: pl, } err := stream.Send(req) if errors.Is(err, io.EOF) { // possible due to context cancellation assert.ErrorIs(t, ctx.Err(), context.Canceled) } else { assert.NoError(t, err) } } assert.NoError(t, stream.CloseSend()) }() wg.Add(1) go func() { defer wg.Done() for i := 0; i < messageCount; i++ { _, err := stream.Recv() if i > messageCount/2 { cancel() } // must continue to receive messages until server acknowledges the cancellation, to ensure no data race happens there too if status.Code(err) == codes.Canceled { return } assert.NoError(t, err) } }() wg.Wait() } } grpc_test.go000066400000000000000000000513221470323427300370220ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/google.golang.org/grpc/otelgrpc/test// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package test import ( "context" "net" "strconv" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials/insecure" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/internal/test" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/instrumentation" "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest" "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/sdk/trace/tracetest" semconv "go.opentelemetry.io/otel/semconv/v1.17.0" pb "google.golang.org/grpc/interop/grpc_testing" ) var wantInstrumentationScope = instrumentation.Scope{ Name: "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc", SchemaURL: "https://opentelemetry.io/schemas/1.17.0", Version: otelgrpc.Version(), } // newGrpcTest creates a grpc server, starts it, and returns the client, closes everything down during test cleanup. func newGrpcTest(t testing.TB, listener net.Listener, cOpt []grpc.DialOption, sOpt []grpc.ServerOption) pb.TestServiceClient { grpcServer := grpc.NewServer(sOpt...) pb.RegisterTestServiceServer(grpcServer, test.NewTestServer()) errCh := make(chan error) go func() { errCh <- grpcServer.Serve(listener) }() t.Cleanup(func() { grpcServer.Stop() assert.NoError(t, <-errCh) }) cOpt = append(cOpt, grpc.WithTransportCredentials(insecure.NewCredentials())) dialAddr := listener.Addr().String() if l, ok := listener.(interface{ Dial() (net.Conn, error) }); ok { dial := func(context.Context, string) (net.Conn, error) { return l.Dial() } cOpt = append(cOpt, grpc.WithContextDialer(dial)) dialAddr = "passthrough:" + dialAddr } conn, err := grpc.NewClient( dialAddr, cOpt..., ) require.NoError(t, err) t.Cleanup(func() { assert.NoError(t, conn.Close()) }) return pb.NewTestServiceClient(conn) } func doCalls(ctx context.Context, client pb.TestServiceClient) { test.DoEmptyUnaryCall(ctx, client) test.DoLargeUnaryCall(ctx, client) test.DoClientStreaming(ctx, client) test.DoServerStreaming(ctx, client) test.DoPingPong(ctx, client) } func TestInterceptors(t *testing.T) { t.Setenv("OTEL_METRICS_EXEMPLAR_FILTER", "always_off") clientUnarySR := tracetest.NewSpanRecorder() clientUnaryTP := trace.NewTracerProvider(trace.WithSpanProcessor(clientUnarySR)) clientStreamSR := tracetest.NewSpanRecorder() clientStreamTP := trace.NewTracerProvider(trace.WithSpanProcessor(clientStreamSR)) serverUnarySR := tracetest.NewSpanRecorder() serverUnaryTP := trace.NewTracerProvider(trace.WithSpanProcessor(serverUnarySR)) serverUnaryMetricReader := metric.NewManualReader() serverUnaryMP := metric.NewMeterProvider(metric.WithReader(serverUnaryMetricReader)) serverStreamSR := tracetest.NewSpanRecorder() serverStreamTP := trace.NewTracerProvider(trace.WithSpanProcessor(serverStreamSR)) listener, err := net.Listen("tcp", "127.0.0.1:0") require.NoError(t, err, "failed to open port") client := newGrpcTest(t, listener, []grpc.DialOption{ //nolint:staticcheck // Interceptors are deprecated and will be removed in the next release. grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor( otelgrpc.WithTracerProvider(clientUnaryTP), otelgrpc.WithMessageEvents(otelgrpc.ReceivedEvents, otelgrpc.SentEvents), )), //nolint:staticcheck // Interceptors are deprecated and will be removed in the next release. grpc.WithStreamInterceptor(otelgrpc.StreamClientInterceptor( otelgrpc.WithTracerProvider(clientStreamTP), otelgrpc.WithMessageEvents(otelgrpc.ReceivedEvents, otelgrpc.SentEvents), )), }, []grpc.ServerOption{ //nolint:staticcheck // Interceptors are deprecated and will be removed in the next release. grpc.UnaryInterceptor(otelgrpc.UnaryServerInterceptor( otelgrpc.WithTracerProvider(serverUnaryTP), otelgrpc.WithMeterProvider(serverUnaryMP), otelgrpc.WithMessageEvents(otelgrpc.ReceivedEvents, otelgrpc.SentEvents), )), //nolint:staticcheck // Interceptors are deprecated and will be removed in the next release. grpc.StreamInterceptor(otelgrpc.StreamServerInterceptor( otelgrpc.WithTracerProvider(serverStreamTP), otelgrpc.WithMessageEvents(otelgrpc.ReceivedEvents, otelgrpc.SentEvents), )), }, ) ctx, cancel := context.WithCancel(context.Background()) defer cancel() doCalls(ctx, client) t.Run("UnaryClientSpans", func(t *testing.T) { checkUnaryClientSpans(t, clientUnarySR.Ended(), listener.Addr().String()) }) t.Run("StreamClientSpans", func(t *testing.T) { checkStreamClientSpans(t, clientStreamSR.Ended(), listener.Addr().String()) }) t.Run("UnaryServerSpans", func(t *testing.T) { checkUnaryServerSpans(t, serverUnarySR.Ended()) checkUnaryServerRecords(t, serverUnaryMetricReader) }) t.Run("StreamServerSpans", func(t *testing.T) { checkStreamServerSpans(t, serverStreamSR.Ended()) }) } func checkUnaryClientSpans(t *testing.T, spans []trace.ReadOnlySpan, addr string) { require.Len(t, spans, 2) host, p, err := net.SplitHostPort(addr) require.NoError(t, err) port, err := strconv.Atoi(p) require.NoError(t, err) emptySpan := spans[0] assert.False(t, emptySpan.EndTime().IsZero()) assert.Equal(t, "grpc.testing.TestService/EmptyCall", emptySpan.Name()) assertEvents(t, []trace.Event{ { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(1), otelgrpc.RPCMessageTypeKey.String("SENT"), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(1), otelgrpc.RPCMessageTypeKey.String("RECEIVED"), }, }, }, emptySpan.Events()) assert.ElementsMatch(t, []attribute.KeyValue{ semconv.RPCMethod("EmptyCall"), semconv.RPCService("grpc.testing.TestService"), otelgrpc.RPCSystemGRPC, otelgrpc.GRPCStatusCodeKey.Int64(int64(codes.OK)), semconv.NetSockPeerAddr(host), semconv.NetSockPeerPort(port), }, emptySpan.Attributes()) largeSpan := spans[1] assert.False(t, largeSpan.EndTime().IsZero()) assert.Equal(t, "grpc.testing.TestService/UnaryCall", largeSpan.Name()) assertEvents(t, []trace.Event{ { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(1), otelgrpc.RPCMessageTypeKey.String("SENT"), // largeReqSize from "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/test" + 12 (overhead). }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(1), otelgrpc.RPCMessageTypeKey.String("RECEIVED"), // largeRespSize from "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/test" + 8 (overhead). }, }, }, largeSpan.Events()) assert.ElementsMatch(t, []attribute.KeyValue{ semconv.RPCMethod("UnaryCall"), semconv.RPCService("grpc.testing.TestService"), otelgrpc.RPCSystemGRPC, otelgrpc.GRPCStatusCodeKey.Int64(int64(codes.OK)), semconv.NetSockPeerAddr(host), semconv.NetSockPeerPort(port), }, largeSpan.Attributes()) } func checkStreamClientSpans(t *testing.T, spans []trace.ReadOnlySpan, addr string) { require.Len(t, spans, 3) host, p, err := net.SplitHostPort(addr) require.NoError(t, err) port, err := strconv.Atoi(p) require.NoError(t, err) streamInput := spans[0] assert.False(t, streamInput.EndTime().IsZero()) assert.Equal(t, "grpc.testing.TestService/StreamingInputCall", streamInput.Name()) // sizes from reqSizes in "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/test". assertEvents(t, []trace.Event{ { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(1), otelgrpc.RPCMessageTypeKey.String("SENT"), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(2), otelgrpc.RPCMessageTypeKey.String("SENT"), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(3), otelgrpc.RPCMessageTypeKey.String("SENT"), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(4), otelgrpc.RPCMessageTypeKey.String("SENT"), }, }, // client does not record an event for the server response. }, streamInput.Events()) assert.ElementsMatch(t, []attribute.KeyValue{ semconv.RPCMethod("StreamingInputCall"), semconv.RPCService("grpc.testing.TestService"), otelgrpc.RPCSystemGRPC, otelgrpc.GRPCStatusCodeKey.Int64(int64(codes.OK)), semconv.NetSockPeerAddr(host), semconv.NetSockPeerPort(port), }, streamInput.Attributes()) streamOutput := spans[1] assert.False(t, streamOutput.EndTime().IsZero()) assert.Equal(t, "grpc.testing.TestService/StreamingOutputCall", streamOutput.Name()) // sizes from respSizes in "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/test". assertEvents(t, []trace.Event{ { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(1), otelgrpc.RPCMessageTypeKey.String("SENT"), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(1), otelgrpc.RPCMessageTypeKey.String("RECEIVED"), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(2), otelgrpc.RPCMessageTypeKey.String("RECEIVED"), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(3), otelgrpc.RPCMessageTypeKey.String("RECEIVED"), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(4), otelgrpc.RPCMessageTypeKey.String("RECEIVED"), }, }, }, streamOutput.Events()) assert.ElementsMatch(t, []attribute.KeyValue{ semconv.RPCMethod("StreamingOutputCall"), semconv.RPCService("grpc.testing.TestService"), otelgrpc.RPCSystemGRPC, otelgrpc.GRPCStatusCodeKey.Int64(int64(codes.OK)), semconv.NetSockPeerAddr(host), semconv.NetSockPeerPort(port), }, streamOutput.Attributes()) pingPong := spans[2] assert.False(t, pingPong.EndTime().IsZero()) assert.Equal(t, "grpc.testing.TestService/FullDuplexCall", pingPong.Name()) assertEvents(t, []trace.Event{ { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(1), otelgrpc.RPCMessageTypeKey.String("SENT"), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(1), otelgrpc.RPCMessageTypeKey.String("RECEIVED"), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(2), otelgrpc.RPCMessageTypeKey.String("SENT"), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(2), otelgrpc.RPCMessageTypeKey.String("RECEIVED"), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(3), otelgrpc.RPCMessageTypeKey.String("SENT"), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(3), otelgrpc.RPCMessageTypeKey.String("RECEIVED"), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(4), otelgrpc.RPCMessageTypeKey.String("SENT"), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(4), otelgrpc.RPCMessageTypeKey.String("RECEIVED"), }, }, }, pingPong.Events()) assert.ElementsMatch(t, []attribute.KeyValue{ semconv.RPCMethod("FullDuplexCall"), semconv.RPCService("grpc.testing.TestService"), otelgrpc.RPCSystemGRPC, otelgrpc.GRPCStatusCodeKey.Int64(int64(codes.OK)), semconv.NetSockPeerAddr(host), semconv.NetSockPeerPort(port), }, pingPong.Attributes()) } func checkStreamServerSpans(t *testing.T, spans []trace.ReadOnlySpan) { require.Len(t, spans, 3) streamInput := spans[0] assert.False(t, streamInput.EndTime().IsZero()) assert.Equal(t, "grpc.testing.TestService/StreamingInputCall", streamInput.Name()) // sizes from reqSizes in "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/test". assertEvents(t, []trace.Event{ { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(1), otelgrpc.RPCMessageTypeKey.String("RECEIVED"), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(2), otelgrpc.RPCMessageTypeKey.String("RECEIVED"), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(3), otelgrpc.RPCMessageTypeKey.String("RECEIVED"), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(4), otelgrpc.RPCMessageTypeKey.String("RECEIVED"), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(1), otelgrpc.RPCMessageTypeKey.String("SENT"), }, }, }, streamInput.Events()) port, ok := findAttribute(streamInput.Attributes(), semconv.NetSockPeerPortKey) assert.True(t, ok) assert.ElementsMatch(t, []attribute.KeyValue{ semconv.RPCMethod("StreamingInputCall"), semconv.RPCService("grpc.testing.TestService"), otelgrpc.RPCSystemGRPC, otelgrpc.GRPCStatusCodeKey.Int64(int64(codes.OK)), semconv.NetSockPeerAddr("127.0.0.1"), port, }, streamInput.Attributes()) streamOutput := spans[1] assert.False(t, streamOutput.EndTime().IsZero()) assert.Equal(t, "grpc.testing.TestService/StreamingOutputCall", streamOutput.Name()) // sizes from respSizes in "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/test". assertEvents(t, []trace.Event{ { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(1), otelgrpc.RPCMessageTypeKey.String("RECEIVED"), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(1), otelgrpc.RPCMessageTypeKey.String("SENT"), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(2), otelgrpc.RPCMessageTypeKey.String("SENT"), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(3), otelgrpc.RPCMessageTypeKey.String("SENT"), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(4), otelgrpc.RPCMessageTypeKey.String("SENT"), }, }, }, streamOutput.Events()) port, ok = findAttribute(streamOutput.Attributes(), semconv.NetSockPeerPortKey) assert.True(t, ok) assert.ElementsMatch(t, []attribute.KeyValue{ semconv.RPCMethod("StreamingOutputCall"), semconv.RPCService("grpc.testing.TestService"), otelgrpc.RPCSystemGRPC, otelgrpc.GRPCStatusCodeKey.Int64(int64(codes.OK)), semconv.NetSockPeerAddr("127.0.0.1"), port, }, streamOutput.Attributes()) pingPong := spans[2] assert.False(t, pingPong.EndTime().IsZero()) assert.Equal(t, "grpc.testing.TestService/FullDuplexCall", pingPong.Name()) assertEvents(t, []trace.Event{ { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(1), otelgrpc.RPCMessageTypeKey.String("RECEIVED"), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(1), otelgrpc.RPCMessageTypeKey.String("SENT"), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(2), otelgrpc.RPCMessageTypeKey.String("RECEIVED"), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(2), otelgrpc.RPCMessageTypeKey.String("SENT"), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(3), otelgrpc.RPCMessageTypeKey.String("RECEIVED"), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(3), otelgrpc.RPCMessageTypeKey.String("SENT"), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(4), otelgrpc.RPCMessageTypeKey.String("RECEIVED"), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(4), otelgrpc.RPCMessageTypeKey.String("SENT"), }, }, }, pingPong.Events()) port, ok = findAttribute(pingPong.Attributes(), semconv.NetSockPeerPortKey) assert.True(t, ok) assert.ElementsMatch(t, []attribute.KeyValue{ semconv.RPCMethod("FullDuplexCall"), semconv.RPCService("grpc.testing.TestService"), otelgrpc.RPCSystemGRPC, otelgrpc.GRPCStatusCodeKey.Int64(int64(codes.OK)), semconv.NetSockPeerAddr("127.0.0.1"), port, }, pingPong.Attributes()) } func checkUnaryServerSpans(t *testing.T, spans []trace.ReadOnlySpan) { require.Len(t, spans, 2) emptySpan := spans[0] assert.False(t, emptySpan.EndTime().IsZero()) assert.Equal(t, "grpc.testing.TestService/EmptyCall", emptySpan.Name()) assertEvents(t, []trace.Event{ { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(1), otelgrpc.RPCMessageTypeKey.String("RECEIVED"), }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(1), otelgrpc.RPCMessageTypeKey.String("SENT"), }, }, }, emptySpan.Events()) port, ok := findAttribute(emptySpan.Attributes(), semconv.NetSockPeerPortKey) assert.True(t, ok) assert.ElementsMatch(t, []attribute.KeyValue{ semconv.RPCMethod("EmptyCall"), semconv.RPCService("grpc.testing.TestService"), otelgrpc.RPCSystemGRPC, otelgrpc.GRPCStatusCodeKey.Int64(int64(codes.OK)), semconv.NetSockPeerAddr("127.0.0.1"), port, }, emptySpan.Attributes()) largeSpan := spans[1] assert.False(t, largeSpan.EndTime().IsZero()) assert.Equal(t, "grpc.testing.TestService/UnaryCall", largeSpan.Name()) assertEvents(t, []trace.Event{ { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(1), otelgrpc.RPCMessageTypeKey.String("RECEIVED"), // largeReqSize from "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/test" + 12 (overhead). }, }, { Name: "message", Attributes: []attribute.KeyValue{ otelgrpc.RPCMessageIDKey.Int(1), otelgrpc.RPCMessageTypeKey.String("SENT"), // largeRespSize from "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/test" + 8 (overhead). }, }, }, largeSpan.Events()) port, ok = findAttribute(largeSpan.Attributes(), semconv.NetSockPeerPortKey) assert.True(t, ok) assert.ElementsMatch(t, []attribute.KeyValue{ semconv.RPCMethod("UnaryCall"), semconv.RPCService("grpc.testing.TestService"), otelgrpc.RPCSystemGRPC, otelgrpc.GRPCStatusCodeKey.Int64(int64(codes.OK)), semconv.NetSockPeerAddr("127.0.0.1"), port, }, largeSpan.Attributes()) } func assertEvents(t *testing.T, expected, actual []trace.Event) bool { //nolint:unparam if !assert.Len(t, actual, len(expected)) { return false } var failed bool for i, e := range expected { if !assert.Equal(t, e.Name, actual[i].Name, "names do not match") { failed = true } if !assert.ElementsMatch(t, e.Attributes, actual[i].Attributes, "attributes do not match: %s", e.Name) { failed = true } } return !failed } func checkUnaryServerRecords(t *testing.T, reader metric.Reader) { rm := metricdata.ResourceMetrics{} err := reader.Collect(context.Background(), &rm) assert.NoError(t, err) require.Len(t, rm.ScopeMetrics, 1) want := metricdata.ScopeMetrics{ Scope: wantInstrumentationScope, Metrics: []metricdata.Metrics{ { Name: "rpc.server.duration", Description: "Measures the duration of inbound RPC.", Unit: "ms", Data: metricdata.Histogram[float64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[float64]{ { Attributes: attribute.NewSet( semconv.RPCMethod("EmptyCall"), semconv.RPCService("grpc.testing.TestService"), otelgrpc.RPCSystemGRPC, otelgrpc.GRPCStatusCodeKey.Int64(int64(codes.OK)), ), }, { Attributes: attribute.NewSet( semconv.RPCMethod("UnaryCall"), semconv.RPCService("grpc.testing.TestService"), otelgrpc.RPCSystemGRPC, otelgrpc.GRPCStatusCodeKey.Int64(int64(codes.OK)), ), }, }, }, }, }, } metricdatatest.AssertEqual(t, want, rm.ScopeMetrics[0], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue()) } func findAttribute(kvs []attribute.KeyValue, key attribute.Key) (attribute.KeyValue, bool) { //nolint:unparam for _, kv := range kvs { if kv.Key == key { return kv, true } } return attribute.KeyValue{}, false } interceptor_test.go000066400000000000000000001066641470323427300404370ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/google.golang.org/grpc/otelgrpc/test// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package test import ( "context" "errors" "io" "net" "strings" "testing" "time" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/internal/test" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest" "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/sdk/trace/tracetest" semconv "go.opentelemetry.io/otel/semconv/v1.17.0" oteltrace "go.opentelemetry.io/otel/trace" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/goleak" "google.golang.org/grpc" grpc_codes "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" "google.golang.org/grpc/test/bufconn" "google.golang.org/grpc/interop/grpc_testing" ) func getSpanFromRecorder(sr *tracetest.SpanRecorder, name string) (trace.ReadOnlySpan, bool) { for _, s := range sr.Ended() { if s.Name() == name { return s, true } } return nil, false } type mockUICInvoker struct { ctx context.Context } func (mcuici *mockUICInvoker) invoker(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, opts ...grpc.CallOption) error { mcuici.ctx = ctx // if method contains error name, mock error return if strings.Contains(method, "error") { return status.Error(grpc_codes.Internal, "internal error") } return nil } func ctxDialer() func(context.Context, string) (net.Conn, error) { l := bufconn.Listen(0) return func(ctx context.Context, _ string) (net.Conn, error) { return l.DialContext(ctx) } } func TestUnaryClientInterceptor(t *testing.T) { clientConn, err := grpc.NewClient("fake:8906", grpc.WithContextDialer(ctxDialer()), grpc.WithTransportCredentials(insecure.NewCredentials()), ) if err != nil { t.Fatalf("failed to create client connection: %v", err) } defer clientConn.Close() sr := tracetest.NewSpanRecorder() tp := trace.NewTracerProvider(trace.WithSpanProcessor(sr)) //nolint:staticcheck // Interceptors are deprecated and will be removed in the next release. unaryInterceptor := otelgrpc.UnaryClientInterceptor( otelgrpc.WithTracerProvider(tp), otelgrpc.WithMessageEvents(otelgrpc.ReceivedEvents, otelgrpc.SentEvents), otelgrpc.WithSpanOptions(oteltrace.WithAttributes(attribute.Bool("custom", true))), ) //nolint:staticcheck // Interceptors are deprecated and will be removed in the next release. unaryInterceptorOnlySentEvents := otelgrpc.UnaryClientInterceptor( otelgrpc.WithTracerProvider(tp), otelgrpc.WithMessageEvents(otelgrpc.SentEvents), ) //nolint:staticcheck // Interceptors are deprecated and will be removed in the next release. unaryInterceptorOnlyReceivedEvents := otelgrpc.UnaryClientInterceptor( otelgrpc.WithTracerProvider(tp), otelgrpc.WithMessageEvents(otelgrpc.ReceivedEvents), ) //nolint:staticcheck // Interceptors are deprecated and will be removed in the next release. unaryInterceptorNoEvents := otelgrpc.UnaryClientInterceptor( otelgrpc.WithTracerProvider(tp), ) req := &grpc_testing.SimpleRequest{} reply := &grpc_testing.SimpleResponse{} uniInterceptorInvoker := &mockUICInvoker{} checks := []struct { method string name string interceptor grpc.UnaryClientInterceptor expectedSpanCode codes.Code expectedAttr []attribute.KeyValue eventsAttr []map[attribute.Key]attribute.Value expectErr bool }{ { method: "/github.com.serviceName/bar", name: "github.com.serviceName/bar", interceptor: unaryInterceptor, expectedAttr: []attribute.KeyValue{ semconv.RPCSystemGRPC, semconv.RPCService("github.com.serviceName"), semconv.RPCMethod("bar"), otelgrpc.GRPCStatusCodeKey.Int64(0), semconv.NetPeerName("fake"), semconv.NetPeerPort(8906), attribute.Bool("custom", true), }, eventsAttr: []map[attribute.Key]attribute.Value{ { otelgrpc.RPCMessageTypeKey: attribute.StringValue("SENT"), otelgrpc.RPCMessageIDKey: attribute.IntValue(1), }, { otelgrpc.RPCMessageTypeKey: attribute.StringValue("RECEIVED"), otelgrpc.RPCMessageIDKey: attribute.IntValue(1), }, }, }, { method: "/serviceName/bar", name: "serviceName/bar", interceptor: unaryInterceptor, expectedAttr: []attribute.KeyValue{ semconv.RPCSystemGRPC, semconv.RPCService("serviceName"), semconv.RPCMethod("bar"), otelgrpc.GRPCStatusCodeKey.Int64(0), semconv.NetPeerName("fake"), semconv.NetPeerPort(8906), attribute.Bool("custom", true), }, eventsAttr: []map[attribute.Key]attribute.Value{ { otelgrpc.RPCMessageTypeKey: attribute.StringValue("SENT"), otelgrpc.RPCMessageIDKey: attribute.IntValue(1), }, { otelgrpc.RPCMessageTypeKey: attribute.StringValue("RECEIVED"), otelgrpc.RPCMessageIDKey: attribute.IntValue(1), }, }, }, { method: "/serviceName/bar_onlysentevents", name: "serviceName/bar_onlysentevents", interceptor: unaryInterceptorOnlySentEvents, expectedAttr: []attribute.KeyValue{ semconv.RPCSystemGRPC, semconv.RPCService("serviceName"), semconv.RPCMethod("bar_onlysentevents"), otelgrpc.GRPCStatusCodeKey.Int64(0), semconv.NetPeerName("fake"), semconv.NetPeerPort(8906), }, eventsAttr: []map[attribute.Key]attribute.Value{ { otelgrpc.RPCMessageTypeKey: attribute.StringValue("SENT"), otelgrpc.RPCMessageIDKey: attribute.IntValue(1), }, }, }, { method: "/serviceName/bar_onlyreceivedevents", name: "serviceName/bar_onlyreceivedevents", interceptor: unaryInterceptorOnlyReceivedEvents, expectedAttr: []attribute.KeyValue{ semconv.RPCSystemGRPC, semconv.RPCService("serviceName"), semconv.RPCMethod("bar_onlyreceivedevents"), otelgrpc.GRPCStatusCodeKey.Int64(0), semconv.NetPeerName("fake"), semconv.NetPeerPort(8906), }, eventsAttr: []map[attribute.Key]attribute.Value{ { otelgrpc.RPCMessageTypeKey: attribute.StringValue("RECEIVED"), otelgrpc.RPCMessageIDKey: attribute.IntValue(1), }, }, }, { method: "/serviceName/bar_noevents", name: "serviceName/bar_noevents", interceptor: unaryInterceptorNoEvents, expectedAttr: []attribute.KeyValue{ semconv.RPCSystemGRPC, semconv.RPCService("serviceName"), semconv.RPCMethod("bar_noevents"), otelgrpc.GRPCStatusCodeKey.Int64(0), semconv.NetPeerName("fake"), semconv.NetPeerPort(8906), }, eventsAttr: []map[attribute.Key]attribute.Value{}, }, { method: "/serviceName/bar", name: "serviceName/bar", interceptor: unaryInterceptor, expectedAttr: []attribute.KeyValue{ semconv.RPCSystemGRPC, semconv.RPCService("serviceName"), semconv.RPCMethod("bar"), otelgrpc.GRPCStatusCodeKey.Int64(int64(grpc_codes.OK)), semconv.NetPeerName("fake"), semconv.NetPeerPort(8906), attribute.Bool("custom", true), }, eventsAttr: []map[attribute.Key]attribute.Value{ { otelgrpc.RPCMessageTypeKey: attribute.StringValue("SENT"), otelgrpc.RPCMessageIDKey: attribute.IntValue(1), }, { otelgrpc.RPCMessageTypeKey: attribute.StringValue("RECEIVED"), otelgrpc.RPCMessageIDKey: attribute.IntValue(1), }, }, }, { method: "/serviceName/bar_error", name: "serviceName/bar_error", interceptor: unaryInterceptor, expectedSpanCode: codes.Error, expectedAttr: []attribute.KeyValue{ semconv.RPCSystemGRPC, semconv.RPCService("serviceName"), semconv.RPCMethod("bar_error"), otelgrpc.GRPCStatusCodeKey.Int64(int64(grpc_codes.Internal)), semconv.NetPeerName("fake"), semconv.NetPeerPort(8906), attribute.Bool("custom", true), }, eventsAttr: []map[attribute.Key]attribute.Value{ { otelgrpc.RPCMessageTypeKey: attribute.StringValue("SENT"), otelgrpc.RPCMessageIDKey: attribute.IntValue(1), }, { otelgrpc.RPCMessageTypeKey: attribute.StringValue("RECEIVED"), otelgrpc.RPCMessageIDKey: attribute.IntValue(1), }, }, expectErr: true, }, { method: "invalidName", name: "invalidName", interceptor: unaryInterceptor, expectedAttr: []attribute.KeyValue{ semconv.RPCSystemGRPC, otelgrpc.GRPCStatusCodeKey.Int64(0), semconv.NetPeerName("fake"), semconv.NetPeerPort(8906), attribute.Bool("custom", true), }, eventsAttr: []map[attribute.Key]attribute.Value{ { otelgrpc.RPCMessageTypeKey: attribute.StringValue("SENT"), otelgrpc.RPCMessageIDKey: attribute.IntValue(1), }, { otelgrpc.RPCMessageTypeKey: attribute.StringValue("RECEIVED"), otelgrpc.RPCMessageIDKey: attribute.IntValue(1), }, }, }, { method: "/github.com.foo.serviceName_123/method", name: "github.com.foo.serviceName_123/method", interceptor: unaryInterceptor, expectedAttr: []attribute.KeyValue{ semconv.RPCSystemGRPC, otelgrpc.GRPCStatusCodeKey.Int64(0), semconv.RPCService("github.com.foo.serviceName_123"), semconv.RPCMethod("method"), semconv.NetPeerName("fake"), semconv.NetPeerPort(8906), attribute.Bool("custom", true), }, eventsAttr: []map[attribute.Key]attribute.Value{ { otelgrpc.RPCMessageTypeKey: attribute.StringValue("SENT"), otelgrpc.RPCMessageIDKey: attribute.IntValue(1), }, { otelgrpc.RPCMessageTypeKey: attribute.StringValue("RECEIVED"), otelgrpc.RPCMessageIDKey: attribute.IntValue(1), }, }, }, } for _, check := range checks { err := check.interceptor(context.Background(), check.method, req, reply, clientConn, uniInterceptorInvoker.invoker) if check.expectErr { assert.Error(t, err) } else { assert.NoError(t, err) } span, ok := getSpanFromRecorder(sr, check.name) if !assert.True(t, ok, "missing span %q", check.name) { continue } assert.Equal(t, check.expectedSpanCode, span.Status().Code) assert.ElementsMatch(t, check.expectedAttr, span.Attributes()) assert.Equal(t, check.eventsAttr, eventAttrMap(span.Events())) } } func eventAttrMap(events []trace.Event) []map[attribute.Key]attribute.Value { maps := make([]map[attribute.Key]attribute.Value, len(events)) for i, event := range events { maps[i] = make(map[attribute.Key]attribute.Value, len(event.Attributes)) for _, a := range event.Attributes { maps[i][a.Key] = a.Value } } return maps } type mockClientStream struct { Desc *grpc.StreamDesc Ctx context.Context msgs []grpc_testing.SimpleResponse } func (mockClientStream) SendMsg(m interface{}) error { return nil } func (c *mockClientStream) RecvMsg(m interface{}) error { if len(c.msgs) == 0 { return io.EOF } c.msgs = c.msgs[1:] return nil } func (mockClientStream) CloseSend() error { return nil } func (c mockClientStream) Context() context.Context { return c.Ctx } func (mockClientStream) Header() (metadata.MD, error) { return nil, nil } func (mockClientStream) Trailer() metadata.MD { return nil } type clientStreamOpts struct { NumRecvMsgs int DisableServerStreams bool Events []otelgrpc.Event } func newMockClientStream(opts clientStreamOpts) *mockClientStream { var msgs []grpc_testing.SimpleResponse for i := 0; i < opts.NumRecvMsgs; i++ { msgs = append(msgs, grpc_testing.SimpleResponse{}) } return &mockClientStream{msgs: msgs} } func createInterceptedStreamClient(t *testing.T, method string, opts clientStreamOpts) (grpc.ClientStream, *tracetest.SpanRecorder) { mockStream := newMockClientStream(opts) clientConn, err := grpc.NewClient("fake:8906", grpc.WithContextDialer(ctxDialer()), grpc.WithTransportCredentials(insecure.NewCredentials()), ) if err != nil { t.Fatalf("failed to create client connection: %v", err) } defer clientConn.Close() // tracer sr := tracetest.NewSpanRecorder() tp := trace.NewTracerProvider(trace.WithSpanProcessor(sr)) interceptorOpts := []otelgrpc.Option{ otelgrpc.WithTracerProvider(tp), otelgrpc.WithSpanOptions(oteltrace.WithAttributes(attribute.Bool("custom", true))), } if len(opts.Events) > 0 { interceptorOpts = append(interceptorOpts, otelgrpc.WithMessageEvents(opts.Events...)) } //nolint:staticcheck // Interceptors are deprecated and will be removed in the next release. streamCI := otelgrpc.StreamClientInterceptor(interceptorOpts...) streamClient, err := streamCI( context.Background(), &grpc.StreamDesc{ServerStreams: !opts.DisableServerStreams}, clientConn, method, func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, opts ...grpc.CallOption, ) (grpc.ClientStream, error) { mockStream.Desc = desc mockStream.Ctx = ctx return mockStream, nil }, ) require.NoError(t, err, "initialize grpc stream client") return streamClient, sr } func TestStreamClientInterceptorOnBIDIStream(t *testing.T) { defer goleak.VerifyNone(t) method := "/github.com.serviceName/bar" name := "github.com.serviceName/bar" opts := clientStreamOpts{ NumRecvMsgs: 10, Events: []otelgrpc.Event{otelgrpc.SentEvents, otelgrpc.ReceivedEvents}, } streamClient, sr := createInterceptedStreamClient(t, method, opts) _, ok := getSpanFromRecorder(sr, name) require.False(t, ok, "span should not end while stream is open") req := &grpc_testing.SimpleRequest{} reply := &grpc_testing.SimpleResponse{} // send and receive fake data for i := 0; i < 10; i++ { _ = streamClient.SendMsg(req) _ = streamClient.RecvMsg(reply) } // The stream has been exhausted so next read should get a EOF and the stream should be considered closed. err := streamClient.RecvMsg(reply) require.Equal(t, io.EOF, err) // wait for span end that is called in separate go routine var span trace.ReadOnlySpan require.Eventually(t, func() bool { span, ok = getSpanFromRecorder(sr, name) return ok }, 5*time.Second, time.Second, "missing span %s", name) expectedAttr := []attribute.KeyValue{ semconv.RPCSystemGRPC, otelgrpc.GRPCStatusCodeKey.Int64(int64(grpc_codes.OK)), semconv.RPCService("github.com.serviceName"), semconv.RPCMethod("bar"), semconv.NetPeerName("fake"), semconv.NetPeerPort(8906), attribute.Bool("custom", true), } assert.ElementsMatch(t, expectedAttr, span.Attributes()) events := span.Events() require.Len(t, events, 20) for i := 0; i < 20; i += 2 { msgID := i/2 + 1 validate := func(eventName string, attrs []attribute.KeyValue) { for _, kv := range attrs { k, v := kv.Key, kv.Value if k == otelgrpc.RPCMessageTypeKey && v.AsString() != eventName { t.Errorf("invalid event on index: %d expecting %s event, receive %s event", i, eventName, v.AsString()) } if k == otelgrpc.RPCMessageIDKey && v != attribute.IntValue(msgID) { t.Errorf("invalid id for message event expected %d received %d", msgID, v.AsInt64()) } } } validate("SENT", events[i].Attributes) validate("RECEIVED", events[i+1].Attributes) } // ensure CloseSend can be subsequently called _ = streamClient.CloseSend() } func TestStreamClientInterceptorEvents(t *testing.T) { testCases := []struct { Name string Events []otelgrpc.Event }{ {Name: "With both events", Events: []otelgrpc.Event{otelgrpc.SentEvents, otelgrpc.ReceivedEvents}}, {Name: "With only sent events", Events: []otelgrpc.Event{otelgrpc.SentEvents}}, {Name: "With only received events", Events: []otelgrpc.Event{otelgrpc.ReceivedEvents}}, {Name: "No events", Events: []otelgrpc.Event{}}, } for _, testCase := range testCases { t.Run(testCase.Name, func(t *testing.T) { defer goleak.VerifyNone(t) method := "/github.com.serviceName/bar" name := "github.com.serviceName/bar" streamClient, sr := createInterceptedStreamClient(t, method, clientStreamOpts{NumRecvMsgs: 1, Events: testCase.Events}) _, ok := getSpanFromRecorder(sr, name) require.False(t, ok, "span should not end while stream is open") req := &grpc_testing.SimpleRequest{} reply := &grpc_testing.SimpleResponse{} var eventsAttr []map[attribute.Key]attribute.Value // send and receive fake data _ = streamClient.SendMsg(req) _ = streamClient.RecvMsg(reply) for _, event := range testCase.Events { switch event { case otelgrpc.SentEvents: eventsAttr = append(eventsAttr, map[attribute.Key]attribute.Value{ otelgrpc.RPCMessageTypeKey: attribute.StringValue("SENT"), otelgrpc.RPCMessageIDKey: attribute.IntValue(1), }, ) case otelgrpc.ReceivedEvents: eventsAttr = append(eventsAttr, map[attribute.Key]attribute.Value{ otelgrpc.RPCMessageTypeKey: attribute.StringValue("RECEIVED"), otelgrpc.RPCMessageIDKey: attribute.IntValue(1), }, ) } } // The stream has been exhausted so next read should get a EOF and the stream should be considered closed. err := streamClient.RecvMsg(reply) require.Equal(t, io.EOF, err) // wait for span end that is called in separate go routine var span trace.ReadOnlySpan require.Eventually(t, func() bool { span, ok = getSpanFromRecorder(sr, name) return ok }, 5*time.Second, time.Second, "missing span %s", name) if len(testCase.Events) == 0 { assert.Empty(t, span.Events()) } else { assert.Len(t, span.Events(), len(eventsAttr)) assert.Equal(t, eventsAttr, eventAttrMap(span.Events())) } // ensure CloseSend can be subsequently called _ = streamClient.CloseSend() }) } } func TestStreamClientInterceptorOnUnidirectionalClientServerStream(t *testing.T) { defer goleak.VerifyNone(t) method := "/github.com.serviceName/bar" name := "github.com.serviceName/bar" opts := clientStreamOpts{ NumRecvMsgs: 1, DisableServerStreams: true, Events: []otelgrpc.Event{otelgrpc.ReceivedEvents, otelgrpc.SentEvents}, } streamClient, sr := createInterceptedStreamClient(t, method, opts) _, ok := getSpanFromRecorder(sr, name) require.False(t, ok, "span should not end while stream is open") req := &grpc_testing.SimpleRequest{} reply := &grpc_testing.SimpleResponse{} // send fake data for i := 0; i < 10; i++ { _ = streamClient.SendMsg(req) } // A real user would call CloseAndRecv() on the generated client which would generate a sequence of CloseSend() // and RecvMsg() calls. _ = streamClient.CloseSend() err := streamClient.RecvMsg(reply) require.NoError(t, err) // wait for span end that is called in separate go routine var span trace.ReadOnlySpan require.Eventually(t, func() bool { span, ok = getSpanFromRecorder(sr, name) return ok }, 5*time.Second, time.Second, "missing span %s", name) expectedAttr := []attribute.KeyValue{ semconv.RPCSystemGRPC, otelgrpc.GRPCStatusCodeKey.Int64(int64(grpc_codes.OK)), semconv.RPCService("github.com.serviceName"), semconv.RPCMethod("bar"), semconv.NetPeerName("fake"), semconv.NetPeerPort(8906), attribute.Bool("custom", true), } assert.ElementsMatch(t, expectedAttr, span.Attributes()) // Note that there's no "RECEIVED" event generated for the server response. This is a bug. events := span.Events() require.Len(t, events, 10) for i := 0; i < 10; i++ { msgID := i + 1 validate := func(eventName string, attrs []attribute.KeyValue) { for _, kv := range attrs { k, v := kv.Key, kv.Value if k == otelgrpc.RPCMessageTypeKey && v.AsString() != eventName { t.Errorf("invalid event on index: %d expecting %s event, receive %s event", i, eventName, v.AsString()) } if k == otelgrpc.RPCMessageIDKey && v != attribute.IntValue(msgID) { t.Errorf("invalid id for message event expected %d received %d", msgID, v.AsInt64()) } } } validate("SENT", events[i].Attributes) } } // TestStreamClientInterceptorCancelContext tests a cancel context situation. // There should be no goleaks. func TestStreamClientInterceptorCancelContext(t *testing.T) { defer goleak.VerifyNone(t) clientConn, err := grpc.NewClient("fake:8906", grpc.WithContextDialer(ctxDialer()), grpc.WithTransportCredentials(insecure.NewCredentials()), ) if err != nil { t.Fatalf("failed to create client connection: %v", err) } defer clientConn.Close() // tracer sr := tracetest.NewSpanRecorder() tp := trace.NewTracerProvider(trace.WithSpanProcessor(sr)) //nolint:staticcheck // Interceptors are deprecated and will be removed in the next release. streamCI := otelgrpc.StreamClientInterceptor( otelgrpc.WithTracerProvider(tp), otelgrpc.WithMessageEvents(otelgrpc.ReceivedEvents, otelgrpc.SentEvents), ) var mockClStr *mockClientStream method := "/github.com.serviceName/bar" name := "github.com.serviceName/bar" // create a context with cancel cancelCtx, cancel := context.WithCancel(context.Background()) defer cancel() streamClient, err := streamCI( cancelCtx, &grpc.StreamDesc{ServerStreams: true}, clientConn, method, func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, opts ...grpc.CallOption, ) (grpc.ClientStream, error) { mockClStr = &mockClientStream{Desc: desc, Ctx: ctx} return mockClStr, nil }, ) require.NoError(t, err, "initialize grpc stream client") _, ok := getSpanFromRecorder(sr, name) require.False(t, ok, "span should not ended while stream is open") req := &grpc_testing.SimpleRequest{} reply := &grpc_testing.SimpleResponse{} // send and receive fake data for i := 0; i < 10; i++ { _ = streamClient.SendMsg(req) _ = streamClient.RecvMsg(reply) } // close client stream _ = streamClient.CloseSend() } // TestStreamClientInterceptorWithError tests a situation that streamer returns an error. func TestStreamClientInterceptorWithError(t *testing.T) { defer goleak.VerifyNone(t) clientConn, err := grpc.NewClient("fake:8906", grpc.WithContextDialer(ctxDialer()), grpc.WithTransportCredentials(insecure.NewCredentials()), ) if err != nil { t.Fatalf("failed to create client connection: %v", err) } defer clientConn.Close() // tracer sr := tracetest.NewSpanRecorder() tp := trace.NewTracerProvider(trace.WithSpanProcessor(sr)) //nolint:staticcheck // Interceptors are deprecated and will be removed in the next release. streamCI := otelgrpc.StreamClientInterceptor( otelgrpc.WithTracerProvider(tp), otelgrpc.WithMessageEvents(otelgrpc.ReceivedEvents, otelgrpc.SentEvents), ) var mockClStr *mockClientStream method := "/github.com.serviceName/bar" name := "github.com.serviceName/bar" streamClient, err := streamCI( context.Background(), &grpc.StreamDesc{ServerStreams: true}, clientConn, method, func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, opts ...grpc.CallOption, ) (grpc.ClientStream, error) { mockClStr = &mockClientStream{Desc: desc, Ctx: ctx} return mockClStr, errors.New("test") }, ) require.Error(t, err, "initialize grpc stream client") assert.IsType(t, &mockClientStream{}, streamClient) span, ok := getSpanFromRecorder(sr, name) require.True(t, ok, "missing span %s", name) expectedAttr := []attribute.KeyValue{ semconv.RPCSystemGRPC, otelgrpc.GRPCStatusCodeKey.Int64(int64(grpc_codes.Unknown)), semconv.RPCService("github.com.serviceName"), semconv.RPCMethod("bar"), semconv.NetPeerName("fake"), semconv.NetPeerPort(8906), } assert.ElementsMatch(t, expectedAttr, span.Attributes()) assert.Equal(t, codes.Error, span.Status().Code) } var serverChecks = []struct { grpcCode grpc_codes.Code wantSpanCode codes.Code wantSpanStatusDescription string }{ { grpcCode: grpc_codes.OK, wantSpanCode: codes.Unset, wantSpanStatusDescription: "", }, { grpcCode: grpc_codes.Canceled, wantSpanCode: codes.Unset, wantSpanStatusDescription: "", }, { grpcCode: grpc_codes.Unknown, wantSpanCode: codes.Error, wantSpanStatusDescription: grpc_codes.Unknown.String(), }, { grpcCode: grpc_codes.InvalidArgument, wantSpanCode: codes.Unset, wantSpanStatusDescription: "", }, { grpcCode: grpc_codes.DeadlineExceeded, wantSpanCode: codes.Error, wantSpanStatusDescription: grpc_codes.DeadlineExceeded.String(), }, { grpcCode: grpc_codes.NotFound, wantSpanCode: codes.Unset, wantSpanStatusDescription: "", }, { grpcCode: grpc_codes.AlreadyExists, wantSpanCode: codes.Unset, wantSpanStatusDescription: "", }, { grpcCode: grpc_codes.PermissionDenied, wantSpanCode: codes.Unset, wantSpanStatusDescription: "", }, { grpcCode: grpc_codes.ResourceExhausted, wantSpanCode: codes.Unset, wantSpanStatusDescription: "", }, { grpcCode: grpc_codes.FailedPrecondition, wantSpanCode: codes.Unset, wantSpanStatusDescription: "", }, { grpcCode: grpc_codes.Aborted, wantSpanCode: codes.Unset, wantSpanStatusDescription: "", }, { grpcCode: grpc_codes.OutOfRange, wantSpanCode: codes.Unset, wantSpanStatusDescription: "", }, { grpcCode: grpc_codes.Unimplemented, wantSpanCode: codes.Error, wantSpanStatusDescription: grpc_codes.Unimplemented.String(), }, { grpcCode: grpc_codes.Internal, wantSpanCode: codes.Error, wantSpanStatusDescription: grpc_codes.Internal.String(), }, { grpcCode: grpc_codes.Unavailable, wantSpanCode: codes.Error, wantSpanStatusDescription: grpc_codes.Unavailable.String(), }, { grpcCode: grpc_codes.DataLoss, wantSpanCode: codes.Error, wantSpanStatusDescription: grpc_codes.DataLoss.String(), }, { grpcCode: grpc_codes.Unauthenticated, wantSpanCode: codes.Unset, wantSpanStatusDescription: "", }, } func assertServerSpan(t *testing.T, wantSpanCode codes.Code, wantSpanStatusDescription string, wantGrpcCode grpc_codes.Code, span trace.ReadOnlySpan) { // validate span status assert.Equal(t, wantSpanCode, span.Status().Code) assert.Equal(t, wantSpanStatusDescription, span.Status().Description) // validate grpc code span attribute var codeAttr attribute.KeyValue for _, a := range span.Attributes() { if a.Key == otelgrpc.GRPCStatusCodeKey { codeAttr = a break } } require.True(t, codeAttr.Valid(), "attributes contain gRPC status code") assert.Equal(t, attribute.Int64Value(int64(wantGrpcCode)), codeAttr.Value) } // TestUnaryServerInterceptor tests the server interceptor for unary RPCs. func TestUnaryServerInterceptor(t *testing.T) { for _, check := range serverChecks { name := check.grpcCode.String() t.Run(name, func(t *testing.T) { t.Setenv("OTEL_METRICS_EXEMPLAR_FILTER", "always_off") sr := tracetest.NewSpanRecorder() tp := trace.NewTracerProvider(trace.WithSpanProcessor(sr)) mr := metric.NewManualReader() mp := metric.NewMeterProvider(metric.WithReader(mr)) //nolint:staticcheck // Interceptors are deprecated and will be removed in the next release. usi := otelgrpc.UnaryServerInterceptor( otelgrpc.WithTracerProvider(tp), otelgrpc.WithMeterProvider(mp), ) serviceName := "TestGrpcService" methodName := serviceName + "/" + name fullMethodName := "/" + methodName // call the unary interceptor grpcErr := status.Error(check.grpcCode, check.grpcCode.String()) handler := func(_ context.Context, _ interface{}) (interface{}, error) { return nil, grpcErr } _, err := usi(context.Background(), &grpc_testing.SimpleRequest{}, &grpc.UnaryServerInfo{FullMethod: fullMethodName}, handler) assert.Equal(t, grpcErr, err) // validate span span, ok := getSpanFromRecorder(sr, methodName) require.True(t, ok, "missing span %s", methodName) assertServerSpan(t, check.wantSpanCode, check.wantSpanStatusDescription, check.grpcCode, span) // validate metric assertServerMetrics(t, mr, serviceName, name, check.grpcCode) }) } } func TestUnaryServerInterceptorEvents(t *testing.T) { testCases := []struct { Name string Events []otelgrpc.Event }{ { Name: "No events", Events: []otelgrpc.Event{}, }, { Name: "With only received events", Events: []otelgrpc.Event{otelgrpc.ReceivedEvents}, }, { Name: "With only sent events", Events: []otelgrpc.Event{otelgrpc.SentEvents}, }, { Name: "With both events", Events: []otelgrpc.Event{otelgrpc.ReceivedEvents, otelgrpc.SentEvents}, }, } for _, testCase := range testCases { t.Run(testCase.Name, func(t *testing.T) { sr := tracetest.NewSpanRecorder() tp := trace.NewTracerProvider(trace.WithSpanProcessor(sr)) opts := []otelgrpc.Option{ otelgrpc.WithTracerProvider(tp), } if len(testCase.Events) > 0 { opts = append(opts, otelgrpc.WithMessageEvents(testCase.Events...)) } //nolint:staticcheck // Interceptors are deprecated and will be removed in the next release. usi := otelgrpc.UnaryServerInterceptor(opts...) grpcCode := grpc_codes.OK name := grpcCode.String() // call the unary interceptor grpcErr := status.Error(grpcCode, name) handler := func(_ context.Context, _ interface{}) (interface{}, error) { return nil, grpcErr } _, err := usi(context.Background(), &grpc_testing.SimpleRequest{}, &grpc.UnaryServerInfo{FullMethod: name}, handler) assert.Equal(t, grpcErr, err) // validate span span, ok := getSpanFromRecorder(sr, name) require.True(t, ok, "missing span %s", name) // validate events and their attributes if len(testCase.Events) == 0 { assert.Empty(t, span.Events()) } else { assert.Len(t, span.Events(), len(testCase.Events)) for i, event := range testCase.Events { switch event { case otelgrpc.ReceivedEvents: assert.ElementsMatch(t, []attribute.KeyValue{ attribute.Key("message.type").String("RECEIVED"), attribute.Key("message.id").Int(1), }, span.Events()[i].Attributes) case otelgrpc.SentEvents: assert.ElementsMatch(t, []attribute.KeyValue{ attribute.Key("message.type").String("SENT"), attribute.Key("message.id").Int(1), }, span.Events()[i].Attributes) } } } }) } } type mockServerStream struct { grpc.ServerStream } func (m *mockServerStream) Context() context.Context { return context.Background() } func (m *mockServerStream) SendMsg(_ interface{}) error { return nil } func (m *mockServerStream) RecvMsg(_ interface{}) error { return nil } // TestStreamServerInterceptor tests the server interceptor for streaming RPCs. func TestStreamServerInterceptor(t *testing.T) { for _, check := range serverChecks { name := check.grpcCode.String() t.Run(name, func(t *testing.T) { sr := tracetest.NewSpanRecorder() tp := trace.NewTracerProvider(trace.WithSpanProcessor(sr)) //nolint:staticcheck // Interceptors are deprecated and will be removed in the next release. usi := otelgrpc.StreamServerInterceptor( otelgrpc.WithTracerProvider(tp), ) // call the stream interceptor grpcErr := status.Error(check.grpcCode, check.grpcCode.String()) handler := func(_ interface{}, _ grpc.ServerStream) error { return grpcErr } err := usi(&grpc_testing.SimpleRequest{}, &mockServerStream{}, &grpc.StreamServerInfo{FullMethod: name}, handler) assert.Equal(t, grpcErr, err) // validate span span, ok := getSpanFromRecorder(sr, name) require.True(t, ok, "missing span %s", name) assertServerSpan(t, check.wantSpanCode, check.wantSpanStatusDescription, check.grpcCode, span) }) } } func TestStreamServerInterceptorEvents(t *testing.T) { testCases := []struct { Name string Events []otelgrpc.Event }{ {Name: "With events", Events: []otelgrpc.Event{otelgrpc.ReceivedEvents, otelgrpc.SentEvents}}, {Name: "With only sent events", Events: []otelgrpc.Event{otelgrpc.SentEvents}}, {Name: "With only received events", Events: []otelgrpc.Event{otelgrpc.ReceivedEvents}}, {Name: "No events", Events: []otelgrpc.Event{}}, } for _, testCase := range testCases { t.Run(testCase.Name, func(t *testing.T) { sr := tracetest.NewSpanRecorder() tp := trace.NewTracerProvider(trace.WithSpanProcessor(sr)) opts := []otelgrpc.Option{ otelgrpc.WithTracerProvider(tp), } if len(testCase.Events) > 0 { opts = append(opts, otelgrpc.WithMessageEvents(testCase.Events...)) } //nolint:staticcheck // Interceptors are deprecated and will be removed in the next release. usi := otelgrpc.StreamServerInterceptor(opts...) stream := &mockServerStream{} grpcCode := grpc_codes.OK name := grpcCode.String() // call the stream interceptor grpcErr := status.Error(grpcCode, name) handler := func(_ interface{}, handlerStream grpc.ServerStream) error { var msg grpc_testing.SimpleRequest err := handlerStream.RecvMsg(&msg) require.NoError(t, err) err = handlerStream.SendMsg(&msg) require.NoError(t, err) return grpcErr } err := usi(&grpc_testing.SimpleRequest{}, stream, &grpc.StreamServerInfo{FullMethod: name}, handler) require.Equal(t, grpcErr, err) // validate span span, ok := getSpanFromRecorder(sr, name) require.True(t, ok, "missing span %s", name) if len(testCase.Events) == 0 { assert.Empty(t, span.Events()) } else { var eventsAttr []map[attribute.Key]attribute.Value for _, event := range testCase.Events { switch event { case otelgrpc.SentEvents: eventsAttr = append(eventsAttr, map[attribute.Key]attribute.Value{ otelgrpc.RPCMessageTypeKey: attribute.StringValue("SENT"), otelgrpc.RPCMessageIDKey: attribute.IntValue(1), }) case otelgrpc.ReceivedEvents: eventsAttr = append(eventsAttr, map[attribute.Key]attribute.Value{ otelgrpc.RPCMessageTypeKey: attribute.StringValue("RECEIVED"), otelgrpc.RPCMessageIDKey: attribute.IntValue(1), }) } } assert.Len(t, span.Events(), len(eventsAttr)) assert.Equal(t, eventsAttr, eventAttrMap(span.Events())) } }) } } func assertServerMetrics(t *testing.T, reader metric.Reader, serviceName, name string, code grpc_codes.Code) { want := metricdata.ScopeMetrics{ Scope: wantInstrumentationScope, Metrics: []metricdata.Metrics{ { Name: "rpc.server.duration", Description: "Measures the duration of inbound RPC.", Unit: "ms", Data: metricdata.Histogram[float64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[float64]{ { Attributes: attribute.NewSet( semconv.RPCMethod(name), semconv.RPCService(serviceName), otelgrpc.RPCSystemGRPC, otelgrpc.GRPCStatusCodeKey.Int64(int64(code)), ), }, }, }, }, }, } rm := metricdata.ResourceMetrics{} err := reader.Collect(context.Background(), &rm) assert.NoError(t, err) require.Len(t, rm.ScopeMetrics, 1) metricdatatest.AssertEqual(t, want, rm.ScopeMetrics[0], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue()) } func BenchmarkStreamClientInterceptor(b *testing.B) { listener, err := net.Listen("tcp", "127.0.0.1:0") require.NoError(b, err, "failed to open port") client := newGrpcTest(b, listener, []grpc.DialOption{ //nolint:staticcheck // Interceptors are deprecated and will be removed in the next release. grpc.WithStreamInterceptor(otelgrpc.StreamClientInterceptor()), }, []grpc.ServerOption{}, ) b.ResetTimer() ctx, cancel := context.WithCancel(context.Background()) defer cancel() for i := 0; i < b.N; i++ { test.DoClientStreaming(ctx, client) } } stats_handler_test.go000066400000000000000000000103361470323427300407220ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/google.golang.org/grpc/otelgrpc/test// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package test import ( "context" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" grpc_codes "google.golang.org/grpc/codes" "google.golang.org/grpc/stats" "google.golang.org/grpc/status" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest" "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/sdk/trace/tracetest" semconv "go.opentelemetry.io/otel/semconv/v1.17.0" ) func TestStatsHandlerHandleRPCServerErrors(t *testing.T) { for _, check := range serverChecks { name := check.grpcCode.String() t.Run(name, func(t *testing.T) { t.Setenv("OTEL_METRICS_EXEMPLAR_FILTER", "always_off") sr := tracetest.NewSpanRecorder() tp := trace.NewTracerProvider(trace.WithSpanProcessor(sr)) mr := metric.NewManualReader() mp := metric.NewMeterProvider(metric.WithReader(mr)) serverHandler := otelgrpc.NewServerHandler( otelgrpc.WithTracerProvider(tp), otelgrpc.WithMeterProvider(mp), otelgrpc.WithMetricAttributes(testMetricAttr), ) serviceName := "TestGrpcService" methodName := serviceName + "/" + name fullMethodName := "/" + methodName // call the server handler ctx := serverHandler.TagRPC(context.Background(), &stats.RPCTagInfo{ FullMethodName: fullMethodName, }) grpcErr := status.Error(check.grpcCode, check.grpcCode.String()) serverHandler.HandleRPC(ctx, &stats.End{ Error: grpcErr, }) // validate span span, ok := getSpanFromRecorder(sr, methodName) require.True(t, ok, "missing span %s", methodName) assertServerSpan(t, check.wantSpanCode, check.wantSpanStatusDescription, check.grpcCode, span) // validate metric assertStatsHandlerServerMetrics(t, mr, serviceName, name, check.grpcCode) }) } } func assertStatsHandlerServerMetrics(t *testing.T, reader metric.Reader, serviceName, name string, code grpc_codes.Code) { want := metricdata.ScopeMetrics{ Scope: wantInstrumentationScope, Metrics: []metricdata.Metrics{ { Name: "rpc.server.duration", Description: "Measures the duration of inbound RPC.", Unit: "ms", Data: metricdata.Histogram[float64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[float64]{ { Attributes: attribute.NewSet( semconv.RPCMethod(name), semconv.RPCService(serviceName), otelgrpc.RPCSystemGRPC, otelgrpc.GRPCStatusCodeKey.Int64(int64(code)), testMetricAttr, ), }, }, }, }, { Name: "rpc.server.requests_per_rpc", Description: "Measures the number of messages received per RPC. Should be 1 for all non-streaming RPCs.", Unit: "{count}", Data: metricdata.Histogram[int64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[int64]{ { Attributes: attribute.NewSet( semconv.RPCMethod(name), semconv.RPCService(serviceName), otelgrpc.RPCSystemGRPC, otelgrpc.GRPCStatusCodeKey.Int64(int64(code)), testMetricAttr, ), }, }, }, }, { Name: "rpc.server.responses_per_rpc", Description: "Measures the number of messages received per RPC. Should be 1 for all non-streaming RPCs.", Unit: "{count}", Data: metricdata.Histogram[int64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[int64]{ { Attributes: attribute.NewSet( semconv.RPCMethod(name), semconv.RPCService(serviceName), otelgrpc.RPCSystemGRPC, otelgrpc.GRPCStatusCodeKey.Int64(int64(code)), testMetricAttr, ), }, }, }, }, }, } rm := metricdata.ResourceMetrics{} err := reader.Collect(context.Background(), &rm) assert.NoError(t, err) require.Len(t, rm.ScopeMetrics, 1) metricdatatest.AssertEqual(t, want, rm.ScopeMetrics[0], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue()) } version.go000066400000000000000000000010471470323427300365140ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/google.golang.org/grpc/otelgrpc/test// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package test // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/test" // Version is the current release version of the gRPC instrumentation test module. func Version() string { return "0.56.0" // This string is updated by the pre_release.sh script during release } // SemVersion is the semantic version to be supplied to tracer/meter creation. // // Deprecated: Use [Version] instead. func SemVersion() string { return Version() } version.go000066400000000000000000000010321470323427300355270ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/google.golang.org/grpc/otelgrpc// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelgrpc // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" // Version is the current release version of the gRPC instrumentation. func Version() string { return "0.56.0" // This string is updated by the pre_release.sh script during release } // SemVersion is the semantic version to be supplied to tracer/meter creation. // // Deprecated: Use [Version] instead. func SemVersion() string { return Version() } open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/host/000077500000000000000000000000001470323427300264715ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/host/doc.go000066400000000000000000000021031470323427300275610ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package host provides the conventional host metric instruments // specified by OpenTelemetry. Host metric events are sometimes // collected through the OpenTelemetry Collector "hostmetrics" // receiver running as an agent; this instrumentation is an // alternative for processes that want to record the same information // without an agent. // // The metric events produced are listed here with attribute dimensions. // // Name Attribute // // ---------------------------------------------------------------------- // // process.cpu.time state=user|system // system.cpu.time state=user|system|other|idle // system.memory.usage state=used|available // system.memory.utilization state=used|available // system.network.io direction=transmit|receive // // See https://github.com/open-telemetry/oteps/blob/main/text/0119-standard-system-metrics.md // for the definition of these metric instruments. package host // import "go.opentelemetry.io/contrib/instrumentation/host" open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/host/example/000077500000000000000000000000001470323427300301245ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/host/example/doc.go000066400000000000000000000002421470323427300312160ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package main provides and example use of the host instrumentation. package main open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/host/example/go.mod000066400000000000000000000021471470323427300312360ustar00rootroot00000000000000module go.opentelemetry.io/contrib/instrumentation/host/example go 1.22 replace go.opentelemetry.io/contrib/instrumentation/host => ../ require ( go.opentelemetry.io/contrib/instrumentation/host v0.56.0 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.31.0 go.opentelemetry.io/otel/sdk v1.31.0 go.opentelemetry.io/otel/sdk/metric v1.31.0 ) require ( github.com/ebitengine/purego v0.8.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/shirou/gopsutil/v4 v4.24.9 // indirect github.com/tklauser/go-sysconf v0.3.14 // indirect github.com/tklauser/numcpus v0.9.0 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect go.opentelemetry.io/otel/trace v1.31.0 // indirect golang.org/x/sys v0.26.0 // indirect ) open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/host/example/go.sum000066400000000000000000000107651470323427300312700ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/ebitengine/purego v0.8.0 h1:JbqvnEzRvPpxhCJzJJ2y0RbiZ8nyjccVUrSM3q+GvvE= github.com/ebitengine/purego v0.8.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 h1:7UMa6KCCMjZEMDtTVdcGu0B1GmmC7QJKiCCjyTAWQy0= github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/shirou/gopsutil/v4 v4.24.9 h1:KIV+/HaHD5ka5f570RZq+2SaeFsb/pq+fp2DGNWYoOI= github.com/shirou/gopsutil/v4 v4.24.9/go.mod h1:3fkaHNeYsUFCGZ8+9vZVWtbyM1k2eRnlL+bWO8Bxa/Q= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU= github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY= github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo= github.com/tklauser/numcpus v0.9.0/go.mod h1:SN6Nq1O3VychhC1npsWostA+oW+VOQTxZrS604NSRyI= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.31.0 h1:HZgBIps9wH0RDrwjrmNa3DVbNRW60HEhdzqZFyAp3fI= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.31.0/go.mod h1:RDRhvt6TDG0eIXmonAx5bd9IcwpqCkziwkOClzWKwAQ= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc= go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/host/example/main.go000066400000000000000000000022641470323427300314030ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 //go:build go1.18 // +build go1.18 package main import ( "context" "log" "os" "os/signal" "time" "go.opentelemetry.io/contrib/instrumentation/host" "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.17.0" ) var res = resource.NewWithAttributes( semconv.SchemaURL, semconv.ServiceName("host-instrumentation-example"), ) func main() { exp, err := stdoutmetric.New() if err != nil { log.Fatal(err) } // Register the exporter with an SDK via a periodic reader. read := metric.NewPeriodicReader(exp, metric.WithInterval(1*time.Second)) provider := metric.NewMeterProvider(metric.WithResource(res), metric.WithReader(read)) defer func() { err := provider.Shutdown(context.Background()) if err != nil { log.Fatal(err) } }() ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) defer cancel() log.Print("Starting host instrumentation:") err = host.Start(host.WithMeterProvider(provider)) if err != nil { log.Fatal(err) } <-ctx.Done() } open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/host/go.mod000066400000000000000000000014161470323427300276010ustar00rootroot00000000000000module go.opentelemetry.io/contrib/instrumentation/host go 1.22 require ( github.com/shirou/gopsutil/v4 v4.24.9 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/metric v1.31.0 ) require ( github.com/ebitengine/purego v0.8.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/tklauser/go-sysconf v0.3.14 // indirect github.com/tklauser/numcpus v0.9.0 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect go.opentelemetry.io/otel/trace v1.31.0 // indirect golang.org/x/sys v0.26.0 // indirect ) open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/host/go.sum000066400000000000000000000073751470323427300276400ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/ebitengine/purego v0.8.0 h1:JbqvnEzRvPpxhCJzJJ2y0RbiZ8nyjccVUrSM3q+GvvE= github.com/ebitengine/purego v0.8.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 h1:7UMa6KCCMjZEMDtTVdcGu0B1GmmC7QJKiCCjyTAWQy0= github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/shirou/gopsutil/v4 v4.24.9 h1:KIV+/HaHD5ka5f570RZq+2SaeFsb/pq+fp2DGNWYoOI= github.com/shirou/gopsutil/v4 v4.24.9/go.mod h1:3fkaHNeYsUFCGZ8+9vZVWtbyM1k2eRnlL+bWO8Bxa/Q= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU= github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY= github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo= github.com/tklauser/numcpus v0.9.0/go.mod h1:SN6Nq1O3VychhC1npsWostA+oW+VOQTxZrS604NSRyI= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/host/host.go000066400000000000000000000206601470323427300300010ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package host // import "go.opentelemetry.io/contrib/instrumentation/host" import ( "context" "fmt" "math" "os" "sync" "github.com/shirou/gopsutil/v4/cpu" "github.com/shirou/gopsutil/v4/mem" "github.com/shirou/gopsutil/v4/net" "github.com/shirou/gopsutil/v4/process" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" ) // ScopeName is the instrumentation scope name. const ScopeName = "go.opentelemetry.io/contrib/instrumentation/host" // Host reports the work-in-progress conventional host metrics specified by OpenTelemetry. type host struct { config config meter metric.Meter } // config contains optional settings for reporting host metrics. type config struct { // MeterProvider sets the metric.MeterProvider. If nil, the global // Provider will be used. MeterProvider metric.MeterProvider } // Option supports configuring optional settings for host metrics. type Option interface { apply(*config) } // WithMeterProvider sets the Metric implementation to use for // reporting. If this option is not used, the global metric.MeterProvider // will be used. `provider` must be non-nil. func WithMeterProvider(provider metric.MeterProvider) Option { return metricProviderOption{provider} } type metricProviderOption struct{ metric.MeterProvider } func (o metricProviderOption) apply(c *config) { if o.MeterProvider != nil { c.MeterProvider = o.MeterProvider } } // Attribute sets. var ( // Attribute sets for CPU time measurements. AttributeCPUTimeUser = attribute.NewSet(attribute.String("state", "user")) AttributeCPUTimeSystem = attribute.NewSet(attribute.String("state", "system")) AttributeCPUTimeOther = attribute.NewSet(attribute.String("state", "other")) AttributeCPUTimeIdle = attribute.NewSet(attribute.String("state", "idle")) // Attribute sets used for Memory measurements. AttributeMemoryAvailable = attribute.NewSet(attribute.String("state", "available")) AttributeMemoryUsed = attribute.NewSet(attribute.String("state", "used")) // Attribute sets used for Network measurements. AttributeNetworkTransmit = attribute.NewSet(attribute.String("direction", "transmit")) AttributeNetworkReceive = attribute.NewSet(attribute.String("direction", "receive")) ) // newConfig computes a config from a list of Options. func newConfig(opts ...Option) config { c := config{ MeterProvider: otel.GetMeterProvider(), } for _, opt := range opts { opt.apply(&c) } return c } // Start initializes reporting of host metrics using the supplied config. func Start(opts ...Option) error { c := newConfig(opts...) if c.MeterProvider == nil { c.MeterProvider = otel.GetMeterProvider() } h := &host{ meter: c.MeterProvider.Meter( ScopeName, metric.WithInstrumentationVersion(Version()), ), config: c, } return h.register() } func (h *host) register() error { var ( err error processCPUTime metric.Float64ObservableCounter hostCPUTime metric.Float64ObservableCounter hostMemoryUsage metric.Int64ObservableGauge hostMemoryUtilization metric.Float64ObservableGauge networkIOUsage metric.Int64ObservableCounter // lock prevents a race between batch observer and instrument registration. lock sync.Mutex ) pid := os.Getpid() if pid > math.MaxInt32 || pid < math.MinInt32 { return fmt.Errorf("invalid process ID: %d", pid) } proc, err := process.NewProcess(int32(pid)) // nolint: gosec // Overflow checked above. if err != nil { return fmt.Errorf("could not find this process: %w", err) } lock.Lock() defer lock.Unlock() // TODO: .time units are in seconds, but "unit" package does // not include this string. // https://github.com/open-telemetry/opentelemetry-specification/issues/705 if processCPUTime, err = h.meter.Float64ObservableCounter( "process.cpu.time", metric.WithUnit("s"), metric.WithDescription( "Accumulated CPU time spent by this process attributed by state (User, System, ...)", ), ); err != nil { return err } if hostCPUTime, err = h.meter.Float64ObservableCounter( "system.cpu.time", metric.WithUnit("s"), metric.WithDescription( "Accumulated CPU time spent by this host attributed by state (User, System, Other, Idle)", ), ); err != nil { return err } if hostMemoryUsage, err = h.meter.Int64ObservableGauge( "system.memory.usage", metric.WithUnit("By"), metric.WithDescription( "Memory usage of this process attributed by memory state (Used, Available)", ), ); err != nil { return err } if hostMemoryUtilization, err = h.meter.Float64ObservableGauge( "system.memory.utilization", metric.WithUnit("1"), metric.WithDescription( "Memory utilization of this process attributed by memory state (Used, Available)", ), ); err != nil { return err } if networkIOUsage, err = h.meter.Int64ObservableCounter( "system.network.io", metric.WithUnit("By"), metric.WithDescription( "Bytes transferred attributed by direction (Transmit, Receive)", ), ); err != nil { return err } _, err = h.meter.RegisterCallback( func(ctx context.Context, o metric.Observer) error { lock.Lock() defer lock.Unlock() // This follows the OpenTelemetry Collector's "hostmetrics" // receiver/hostmetricsreceiver/internal/scraper/processscraper // measures User and System IOwait time. // TODO: the Collector has per-OS compilation modules to support // specific metrics that are not universal. processTimes, err := proc.TimesWithContext(ctx) if err != nil { return err } hostTimeSlice, err := cpu.TimesWithContext(ctx, false) if err != nil { return err } if len(hostTimeSlice) != 1 { return fmt.Errorf("host CPU usage: incorrect summary count") } vmStats, err := mem.VirtualMemoryWithContext(ctx) if err != nil { return err } ioStats, err := net.IOCountersWithContext(ctx, false) if err != nil { return err } if len(ioStats) != 1 { return fmt.Errorf("host network usage: incorrect summary count") } hostTime := hostTimeSlice[0] opt := metric.WithAttributeSet(AttributeCPUTimeUser) o.ObserveFloat64(processCPUTime, processTimes.User, opt) o.ObserveFloat64(hostCPUTime, hostTime.User, opt) opt = metric.WithAttributeSet(AttributeCPUTimeSystem) o.ObserveFloat64(processCPUTime, processTimes.System, opt) o.ObserveFloat64(hostCPUTime, hostTime.System, opt) // TODO(#244): "other" is a placeholder for actually dealing // with these states. Do users actually want this // (unconditionally)? How should we handle "iowait" // if not all systems expose it? Should we break // these down by CPU? If so, are users going to want // to aggregate in-process? See: // https://github.com/open-telemetry/opentelemetry-go-contrib/issues/244 other := hostTime.Nice + hostTime.Iowait + hostTime.Irq + hostTime.Softirq + hostTime.Steal + hostTime.Guest + hostTime.GuestNice opt = metric.WithAttributeSet(AttributeCPUTimeOther) o.ObserveFloat64(hostCPUTime, other, opt) opt = metric.WithAttributeSet(AttributeCPUTimeIdle) o.ObserveFloat64(hostCPUTime, hostTime.Idle, opt) // Host memory usage opt = metric.WithAttributeSet(AttributeMemoryUsed) o.ObserveInt64(hostMemoryUsage, clampInt64(vmStats.Used), opt) opt = metric.WithAttributeSet(AttributeMemoryAvailable) o.ObserveInt64(hostMemoryUsage, clampInt64(vmStats.Available), opt) // Host memory utilization opt = metric.WithAttributeSet(AttributeMemoryUsed) o.ObserveFloat64(hostMemoryUtilization, float64(vmStats.Used)/float64(vmStats.Total), opt) opt = metric.WithAttributeSet(AttributeMemoryAvailable) o.ObserveFloat64(hostMemoryUtilization, float64(vmStats.Available)/float64(vmStats.Total), opt) // Host network usage // // TODO: These can be broken down by network // interface, with similar questions to those posed // about per-CPU measurements above. opt = metric.WithAttributeSet(AttributeNetworkTransmit) o.ObserveInt64(networkIOUsage, clampInt64(ioStats[0].BytesSent), opt) opt = metric.WithAttributeSet(AttributeNetworkReceive) o.ObserveInt64(networkIOUsage, clampInt64(ioStats[0].BytesRecv), opt) return nil }, processCPUTime, hostCPUTime, hostMemoryUsage, hostMemoryUtilization, networkIOUsage, ) if err != nil { return err } return nil } func clampInt64(v uint64) int64 { if v > math.MaxInt64 { return math.MaxInt64 } return int64(v) // nolint: gosec // Overflow checked. } open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/host/host_test.go000066400000000000000000000006401470323427300310340ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package host_test // TODO(#2755): Add integration tests for the host instrumentation. These tests // depend on https://github.com/open-telemetry/opentelemetry-go/issues/3031 // being resolved. // // The added tests will depend on the metric SDK. Therefore, they should be // added to a sub-directory called "test" instead of this file. open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/host/version.go000066400000000000000000000007731470323427300305140ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package host // import "go.opentelemetry.io/contrib/instrumentation/host" // Version is the current release version of the host instrumentation. func Version() string { return "0.56.0" // This string is updated by the pre_release.sh script during release } // SemVersion is the semantic version to be supplied to tracer/meter creation. // // Deprecated: Use [Version] instead. func SemVersion() string { return Version() } open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/000077500000000000000000000000001470323427300263025ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/000077500000000000000000000000001470323427300272615ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/httptrace/000077500000000000000000000000001470323427300312575ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/httptrace/otelhttptrace/000077500000000000000000000000001470323427300341415ustar00rootroot00000000000000api.go000066400000000000000000000007051470323427300351640ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/httptrace/otelhttptrace// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelhttptrace // import "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace" import ( "context" "net/http" "net/http/httptrace" ) // W3C client. func W3C(ctx context.Context, req *http.Request) (context.Context, *http.Request) { ctx = httptrace.WithClientTrace(ctx, NewClientTrace(ctx)) req = req.WithContext(ctx) return ctx, req } clienttrace.go000066400000000000000000000265051470323427300367160ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/httptrace/otelhttptrace// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelhttptrace // import "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace" import ( "context" "crypto/tls" "net/http/httptrace" "net/textproto" "strings" "sync" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" semconv "go.opentelemetry.io/otel/semconv/v1.20.0" "go.opentelemetry.io/otel/trace" ) // ScopeName is the instrumentation scope name. const ScopeName = "go.opentelemetry.io/otel/instrumentation/httptrace" // HTTP attributes. var ( HTTPStatus = attribute.Key("http.status") HTTPHeaderMIME = attribute.Key("http.mime") HTTPRemoteAddr = attribute.Key("http.remote") HTTPLocalAddr = attribute.Key("http.local") HTTPConnectionReused = attribute.Key("http.conn.reused") HTTPConnectionWasIdle = attribute.Key("http.conn.wasidle") HTTPConnectionIdleTime = attribute.Key("http.conn.idletime") HTTPConnectionStartNetwork = attribute.Key("http.conn.start.network") HTTPConnectionDoneNetwork = attribute.Key("http.conn.done.network") HTTPConnectionDoneAddr = attribute.Key("http.conn.done.addr") HTTPDNSAddrs = attribute.Key("http.dns.addrs") ) var hookMap = map[string]string{ "http.dns": "http.getconn", "http.connect": "http.getconn", "http.tls": "http.getconn", } func parentHook(hook string) string { if strings.HasPrefix(hook, "http.connect") { return hookMap["http.connect"] } return hookMap[hook] } // ClientTraceOption allows customizations to how the httptrace.Client // collects information. type ClientTraceOption interface { apply(*clientTracer) } type clientTraceOptionFunc func(*clientTracer) func (fn clientTraceOptionFunc) apply(c *clientTracer) { fn(c) } // WithoutSubSpans will modify the httptrace.ClientTrace to only collect data // as Events and Attributes on a span found in the context. By default // sub-spans will be generated. func WithoutSubSpans() ClientTraceOption { return clientTraceOptionFunc(func(ct *clientTracer) { ct.useSpans = false }) } // WithRedactedHeaders will be replaced by fixed '****' values for the header // names provided. These are in addition to the sensitive headers already // redacted by default: Authorization, WWW-Authenticate, Proxy-Authenticate // Proxy-Authorization, Cookie, Set-Cookie. func WithRedactedHeaders(headers ...string) ClientTraceOption { return clientTraceOptionFunc(func(ct *clientTracer) { for _, header := range headers { ct.redactedHeaders[strings.ToLower(header)] = struct{}{} } }) } // WithoutHeaders will disable adding span attributes for the http headers // and values. func WithoutHeaders() ClientTraceOption { return clientTraceOptionFunc(func(ct *clientTracer) { ct.addHeaders = false }) } // WithInsecureHeaders will add span attributes for all http headers *INCLUDING* // the sensitive headers that are redacted by default. The attribute values // will include the raw un-redacted text. This might be useful for // debugging authentication related issues, but should not be used for // production deployments. func WithInsecureHeaders() ClientTraceOption { return clientTraceOptionFunc(func(ct *clientTracer) { ct.addHeaders = true ct.redactedHeaders = nil }) } // WithTracerProvider specifies a tracer provider for creating a tracer. // The global provider is used if none is specified. func WithTracerProvider(provider trace.TracerProvider) ClientTraceOption { return clientTraceOptionFunc(func(ct *clientTracer) { if provider != nil { ct.tracerProvider = provider } }) } type clientTracer struct { context.Context tracerProvider trace.TracerProvider tr trace.Tracer activeHooks map[string]context.Context root trace.Span mtx sync.Mutex redactedHeaders map[string]struct{} addHeaders bool useSpans bool } // NewClientTrace returns an httptrace.ClientTrace implementation that will // record OpenTelemetry spans for requests made by an http.Client. By default // several spans will be added to the trace for various stages of a request // (dns, connection, tls, etc). Also by default, all HTTP headers will be // added as attributes to spans, although several headers will be automatically // redacted: Authorization, WWW-Authenticate, Proxy-Authenticate, // Proxy-Authorization, Cookie, and Set-Cookie. func NewClientTrace(ctx context.Context, opts ...ClientTraceOption) *httptrace.ClientTrace { ct := &clientTracer{ Context: ctx, activeHooks: make(map[string]context.Context), redactedHeaders: map[string]struct{}{ "authorization": {}, "www-authenticate": {}, "proxy-authenticate": {}, "proxy-authorization": {}, "cookie": {}, "set-cookie": {}, }, addHeaders: true, useSpans: true, } if span := trace.SpanFromContext(ctx); span.SpanContext().IsValid() { ct.tracerProvider = span.TracerProvider() } else { ct.tracerProvider = otel.GetTracerProvider() } for _, opt := range opts { opt.apply(ct) } ct.tr = ct.tracerProvider.Tracer( ScopeName, trace.WithInstrumentationVersion(Version()), ) return &httptrace.ClientTrace{ GetConn: ct.getConn, GotConn: ct.gotConn, PutIdleConn: ct.putIdleConn, GotFirstResponseByte: ct.gotFirstResponseByte, Got100Continue: ct.got100Continue, Got1xxResponse: ct.got1xxResponse, DNSStart: ct.dnsStart, DNSDone: ct.dnsDone, ConnectStart: ct.connectStart, ConnectDone: ct.connectDone, TLSHandshakeStart: ct.tlsHandshakeStart, TLSHandshakeDone: ct.tlsHandshakeDone, WroteHeaderField: ct.wroteHeaderField, WroteHeaders: ct.wroteHeaders, Wait100Continue: ct.wait100Continue, WroteRequest: ct.wroteRequest, } } func (ct *clientTracer) start(hook, spanName string, attrs ...attribute.KeyValue) { if !ct.useSpans { if ct.root == nil { ct.root = trace.SpanFromContext(ct.Context) } ct.root.AddEvent(hook+".start", trace.WithAttributes(attrs...)) return } ct.mtx.Lock() defer ct.mtx.Unlock() if hookCtx, found := ct.activeHooks[hook]; !found { var sp trace.Span ct.activeHooks[hook], sp = ct.tr.Start(ct.getParentContext(hook), spanName, trace.WithAttributes(attrs...), trace.WithSpanKind(trace.SpanKindClient)) if ct.root == nil { ct.root = sp } } else { // end was called before start finished, add the start attributes and end the span here span := trace.SpanFromContext(hookCtx) span.SetAttributes(attrs...) span.End() delete(ct.activeHooks, hook) } } func (ct *clientTracer) end(hook string, err error, attrs ...attribute.KeyValue) { if !ct.useSpans { // sometimes end may be called without previous start if ct.root == nil { ct.root = trace.SpanFromContext(ct.Context) } if err != nil { attrs = append(attrs, attribute.String(hook+".error", err.Error())) } ct.root.AddEvent(hook+".done", trace.WithAttributes(attrs...)) return } ct.mtx.Lock() defer ct.mtx.Unlock() if ctx, ok := ct.activeHooks[hook]; ok { span := trace.SpanFromContext(ctx) if err != nil { span.SetStatus(codes.Error, err.Error()) } span.SetAttributes(attrs...) span.End() delete(ct.activeHooks, hook) } else { // start is not finished before end is called. // Start a span here with the ending attributes that will be finished when start finishes. // Yes, it's backwards. v0v ctx, span := ct.tr.Start(ct.getParentContext(hook), hook, trace.WithAttributes(attrs...), trace.WithSpanKind(trace.SpanKindClient)) if err != nil { span.SetStatus(codes.Error, err.Error()) } ct.activeHooks[hook] = ctx } } func (ct *clientTracer) getParentContext(hook string) context.Context { ctx, ok := ct.activeHooks[parentHook(hook)] if !ok { return ct.Context } return ctx } func (ct *clientTracer) span(hook string) trace.Span { ct.mtx.Lock() defer ct.mtx.Unlock() if ctx, ok := ct.activeHooks[hook]; ok { return trace.SpanFromContext(ctx) } return nil } func (ct *clientTracer) getConn(host string) { ct.start("http.getconn", "http.getconn", semconv.NetHostName(host)) } func (ct *clientTracer) gotConn(info httptrace.GotConnInfo) { attrs := []attribute.KeyValue{ HTTPRemoteAddr.String(info.Conn.RemoteAddr().String()), HTTPLocalAddr.String(info.Conn.LocalAddr().String()), HTTPConnectionReused.Bool(info.Reused), HTTPConnectionWasIdle.Bool(info.WasIdle), } if info.WasIdle { attrs = append(attrs, HTTPConnectionIdleTime.String(info.IdleTime.String())) } ct.end("http.getconn", nil, attrs...) } func (ct *clientTracer) putIdleConn(err error) { ct.end("http.receive", err) } func (ct *clientTracer) gotFirstResponseByte() { ct.start("http.receive", "http.receive") } func (ct *clientTracer) dnsStart(info httptrace.DNSStartInfo) { ct.start("http.dns", "http.dns", semconv.NetHostName(info.Host)) } func (ct *clientTracer) dnsDone(info httptrace.DNSDoneInfo) { var addrs []string for _, netAddr := range info.Addrs { addrs = append(addrs, netAddr.String()) } ct.end("http.dns", info.Err, HTTPDNSAddrs.String(sliceToString(addrs))) } func (ct *clientTracer) connectStart(network, addr string) { ct.start("http.connect."+addr, "http.connect", HTTPRemoteAddr.String(addr), HTTPConnectionStartNetwork.String(network), ) } func (ct *clientTracer) connectDone(network, addr string, err error) { ct.end("http.connect."+addr, err, HTTPConnectionDoneAddr.String(addr), HTTPConnectionDoneNetwork.String(network), ) } func (ct *clientTracer) tlsHandshakeStart() { ct.start("http.tls", "http.tls") } func (ct *clientTracer) tlsHandshakeDone(_ tls.ConnectionState, err error) { ct.end("http.tls", err) } func (ct *clientTracer) wroteHeaderField(k string, v []string) { if ct.useSpans && ct.span("http.headers") == nil { ct.start("http.headers", "http.headers") } if !ct.addHeaders { return } k = strings.ToLower(k) value := sliceToString(v) if _, ok := ct.redactedHeaders[k]; ok { value = "****" } ct.root.SetAttributes(attribute.String("http.request.header."+k, value)) } func (ct *clientTracer) wroteHeaders() { if ct.useSpans && ct.span("http.headers") != nil { ct.end("http.headers", nil) } ct.start("http.send", "http.send") } func (ct *clientTracer) wroteRequest(info httptrace.WroteRequestInfo) { if info.Err != nil { ct.root.SetStatus(codes.Error, info.Err.Error()) } ct.end("http.send", info.Err) } func (ct *clientTracer) got100Continue() { span := ct.root if ct.useSpans { span = ct.span("http.receive") } span.AddEvent("GOT 100 - Continue") } func (ct *clientTracer) wait100Continue() { span := ct.root if ct.useSpans { span = ct.span("http.send") } span.AddEvent("GOT 100 - Wait") } func (ct *clientTracer) got1xxResponse(code int, header textproto.MIMEHeader) error { span := ct.root if ct.useSpans { span = ct.span("http.receive") } span.AddEvent("GOT 1xx", trace.WithAttributes( HTTPStatus.Int(code), HTTPHeaderMIME.String(sm2s(header)), )) return nil } func sliceToString(value []string) string { if len(value) == 0 { return "undefined" } return strings.Join(value, ",") } func sm2s(value map[string][]string) string { var buf strings.Builder for k, v := range value { if buf.Len() != 0 { _, _ = buf.WriteString(",") } _, _ = buf.WriteString(k) _, _ = buf.WriteString("=") _, _ = buf.WriteString(sliceToString(v)) } return buf.String() } clienttrace_test.go000066400000000000000000000011621470323427300377450ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/httptrace/otelhttptrace// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelhttptrace import ( "context" "fmt" "net/http" "net/http/httptrace" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) func ExampleNewClientTrace() { client := http.Client{ Transport: otelhttp.NewTransport( http.DefaultTransport, otelhttp.WithClientTrace(func(ctx context.Context) *httptrace.ClientTrace { return NewClientTrace(ctx) }), ), } resp, err := client.Get("https://example.com") if err != nil { fmt.Println(err) return } defer resp.Body.Close() fmt.Println(resp.Status) } example/000077500000000000000000000000001470323427300355155ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/httptrace/otelhttptraceDockerfile000066400000000000000000000005771470323427300375200ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/httptrace/otelhttptrace/example# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 FROM golang:1.23-alpine AS base COPY . /src/ WORKDIR /src/instrumentation/net/http/httptrace/otelhttptrace/example FROM base AS example-httptrace-server RUN go install ./server/server.go CMD ["/go/bin/server"] FROM base AS example-httptrace-client RUN go install ./client/client.go CMD ["/go/bin/client"] README.md000066400000000000000000000013251470323427300367750ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/httptrace/otelhttptrace/example# HTTP Client-Server Example An HTTP client connects to an HTTP server. They both generate span information to `stdout`. These instructions expect you have [docker-compose](https://docs.docker.com/compose/) installed. Bring up the `http-server` and `http-client` services to run the example: ```sh docker-compose up --detach http-server http-client ``` The `http-client` service sends just one HTTP request to `http-server` and then exits. View the span generated to `stdout` in the logs: ```sh docker-compose logs http-client ``` View the span generated by `http-server` in the logs: ```sh docker-compose logs http-server ``` Shut down the services when you are finished with the example: ```sh docker-compose down ``` client/000077500000000000000000000000001470323427300367735ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/httptrace/otelhttptrace/exampleclient.go000066400000000000000000000054471470323427300406120ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/httptrace/otelhttptrace/example/client// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package main import ( "context" "flag" "fmt" "io" "log" "net/http" "net/http/httptrace" "time" "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/baggage" stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/propagation" sdktrace "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.20.0" "go.opentelemetry.io/otel/trace" ) func initTracer() (*sdktrace.TracerProvider, error) { // Create stdout exporter to be able to retrieve // the collected spans. exporter, err := stdout.New(stdout.WithPrettyPrint()) if err != nil { return nil, err } // For the demonstration, use sdktrace.AlwaysSample sampler to sample all traces. // In a production application, use sdktrace.ProbabilitySampler with a desired probability. tp := sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithBatcher(exporter), ) otel.SetTracerProvider(tp) otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) return tp, nil } func main() { tp, err := initTracer() if err != nil { log.Fatal(err) } defer func() { if err := tp.Shutdown(context.Background()); err != nil { log.Printf("Error shutting down tracer provider: %v", err) } }() url := flag.String("server", "http://localhost:7777/hello", "server url") flag.Parse() client := http.Client{ Transport: otelhttp.NewTransport( http.DefaultTransport, // By setting the otelhttptrace client in this transport, it can be // injected into the context after the span is started, which makes the // httptrace spans children of the transport one. otelhttp.WithClientTrace(func(ctx context.Context) *httptrace.ClientTrace { return otelhttptrace.NewClientTrace(ctx) }), ), } bag, _ := baggage.Parse("username=donuts") ctx := baggage.ContextWithBaggage(context.Background(), bag) var body []byte tr := otel.Tracer("example/client") err = func(ctx context.Context) error { ctx, span := tr.Start(ctx, "say hello", trace.WithAttributes(semconv.PeerService("ExampleService"))) defer span.End() req, _ := http.NewRequestWithContext(ctx, "GET", *url, nil) fmt.Printf("Sending request...\n") res, err := client.Do(req) if err != nil { panic(err) } body, err = io.ReadAll(res.Body) _ = res.Body.Close() return err }(ctx) if err != nil { log.Fatal(err) } fmt.Printf("Response Received: %s\n\n\n", body) fmt.Printf("Waiting for few seconds to export spans ...\n\n") time.Sleep(10 * time.Second) fmt.Printf("Inspect traces on stdout\n") } docker-compose.yml000066400000000000000000000010561470323427300411540ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/httptrace/otelhttptrace/example# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 version: "3.7" services: http-server: build: dockerfile: $PWD/Dockerfile context: ../../../../../.. target: example-httptrace-server networks: - example http-client: build: dockerfile: $PWD/Dockerfile context: ../../../../../.. target: example-httptrace-client command: ["/go/bin/client", "-server", "http://http-server:7777/hello"] networks: - example depends_on: - http-server networks: example: go.mod000066400000000000000000000016201470323427300366220ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/httptrace/otelhttptrace/examplemodule go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace/example go 1.22 replace ( go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace => ../ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp => ../../../otelhttp ) require ( go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.56.0 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0 go.opentelemetry.io/otel/sdk v1.31.0 go.opentelemetry.io/otel/trace v1.31.0 ) require ( github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect golang.org/x/sys v0.26.0 // indirect ) go.sum000066400000000000000000000051731470323427300366560ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/httptrace/otelhttptrace/examplegithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0 h1:UGZ1QwZWY67Z6BmckTU+9Rxn04m2bD3gD6Mk0OIOCPk= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0/go.mod h1:fcwWuDuaObkkChiDlhEpSq9+X1C0omv+s5mBtToAQ64= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= server/000077500000000000000000000000001470323427300370235ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/httptrace/otelhttptrace/examplemodd.conf000066400000000000000000000004161470323427300406160ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/httptrace/otelhttptrace/example/server# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 # A basic modd.conf file for Go development. # Run go test on ALL modules on startup, and subsequently only on modules # containing changes. server.go { daemon +sigterm: go run server.go }server.go000066400000000000000000000043211470323427300406600ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/httptrace/otelhttptrace/example/server// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package main import ( "context" "io" "log" "net/http" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/baggage" stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.20.0" "go.opentelemetry.io/otel/trace" ) func initTracer() (*sdktrace.TracerProvider, error) { // Create stdout exporter to be able to retrieve // the collected spans. exporter, err := stdout.New(stdout.WithPrettyPrint()) if err != nil { return nil, err } // For the demonstration, use sdktrace.AlwaysSample sampler to sample all traces. // In a production application, use sdktrace.ProbabilitySampler with a desired probability. tp := sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithBatcher(exporter), sdktrace.WithResource(resource.NewWithAttributes(semconv.SchemaURL, semconv.ServiceName("ExampleService"))), ) otel.SetTracerProvider(tp) otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) return tp, nil } func main() { tp, err := initTracer() if err != nil { log.Fatal(err) } defer func() { if err := tp.Shutdown(context.Background()); err != nil { log.Printf("Error shutting down tracer provider: %v", err) } }() uk := attribute.Key("username") helloHandler := func(w http.ResponseWriter, req *http.Request) { ctx := req.Context() span := trace.SpanFromContext(ctx) bag := baggage.FromContext(ctx) span.AddEvent("handling this...", trace.WithAttributes(uk.String(bag.Member("username").Value()))) _, _ = io.WriteString(w, "Hello, world!\n") } otelHandler := otelhttp.NewHandler(http.HandlerFunc(helloHandler), "Hello") http.Handle("/hello", otelHandler) err = http.ListenAndServe(":7777", nil) //nolint:gosec // Ignoring G114: Use of net/http serve function that has no support for setting timeouts. if err != nil { log.Fatal(err) } } go.mod000066400000000000000000000013521470323427300351710ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/httptrace/otelhttptracemodule go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace go 1.22 require ( github.com/google/go-cmp v0.6.0 github.com/stretchr/testify v1.9.0 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/trace v1.31.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) replace go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp => ../../otelhttp go.sum000066400000000000000000000041741470323427300352230ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/httptrace/otelhttptracegithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= httptrace.go000066400000000000000000000040421470323427300364070ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/httptrace/otelhttptrace// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelhttptrace // import "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace" import ( "context" "net/http" "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace/internal/semconvutil" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/baggage" "go.opentelemetry.io/otel/propagation" semconv "go.opentelemetry.io/otel/semconv/v1.20.0" "go.opentelemetry.io/otel/trace" ) // Option allows configuration of the httptrace Extract() // and Inject() functions. type Option interface { apply(*config) } type optionFunc func(*config) func (o optionFunc) apply(c *config) { o(c) } type config struct { propagators propagation.TextMapPropagator } func newConfig(opts []Option) *config { c := &config{propagators: otel.GetTextMapPropagator()} for _, o := range opts { o.apply(c) } return c } // WithPropagators sets the propagators to use for Extraction and Injection. func WithPropagators(props propagation.TextMapPropagator) Option { return optionFunc(func(c *config) { if props != nil { c.propagators = props } }) } // Extract returns the Attributes, Context Entries, and SpanContext that were encoded by Inject. func Extract(ctx context.Context, req *http.Request, opts ...Option) ([]attribute.KeyValue, baggage.Baggage, trace.SpanContext) { c := newConfig(opts) ctx = c.propagators.Extract(ctx, propagation.HeaderCarrier(req.Header)) attrs := append(semconvutil.HTTPServerRequest("", req), semconvutil.NetTransport("tcp")) if req.ContentLength > 0 { a := semconv.HTTPRequestContentLength(int(req.ContentLength)) attrs = append(attrs, a) } return attrs, baggage.FromContext(ctx), trace.SpanContextFromContext(ctx) } // Inject sets attributes, context entries, and span context from ctx into // the request. func Inject(ctx context.Context, req *http.Request, opts ...Option) { c := newConfig(opts) c.propagators.Inject(ctx, propagation.HeaderCarrier(req.Header)) } httptrace_test.go000066400000000000000000000111171470323427300374470ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/httptrace/otelhttptrace// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelhttptrace_test import ( "context" "net/http" "net/http/httptest" "strings" "testing" "github.com/google/go-cmp/cmp" "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/baggage" "go.opentelemetry.io/otel/propagation" semconv "go.opentelemetry.io/otel/semconv/v1.20.0" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace/noop" ) func TestRoundtrip(t *testing.T) { tr := noop.NewTracerProvider().Tracer("httptrace/client") var expectedAttrs map[attribute.Key]string expectedCorrs := map[string]string{"foo": "bar"} props := otelhttptrace.WithPropagators(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) // Mock http server ts := httptest.NewServer( http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { attrs, corrs, span := otelhttptrace.Extract(r.Context(), r, props) actualAttrs := make(map[attribute.Key]string) for _, attr := range attrs { if attr.Key == semconv.NetSockPeerPortKey { // Peer port will be non-deterministic continue } actualAttrs[attr.Key] = attr.Value.Emit() } if diff := cmp.Diff(actualAttrs, expectedAttrs); diff != "" { t.Fatalf("[TestRoundtrip] Attributes are different: %v", diff) } actualCorrs := make(map[string]string) for _, corr := range corrs.Members() { actualCorrs[corr.Key()] = corr.Value() } if diff := cmp.Diff(actualCorrs, expectedCorrs); diff != "" { t.Fatalf("[TestRoundtrip] Correlations are different: %v", diff) } if !span.IsValid() { t.Fatalf("[TestRoundtrip] Invalid span extracted: %v", span) } _, err := w.Write([]byte("OK")) if err != nil { t.Fatal(err) } }), ) defer ts.Close() address := ts.Listener.Addr() hp := strings.Split(address.String(), ":") expectedAttrs = map[attribute.Key]string{ semconv.NetHostNameKey: hp[0], semconv.NetHostPortKey: hp[1], semconv.NetProtocolVersionKey: "1.1", semconv.HTTPMethodKey: "GET", semconv.HTTPSchemeKey: "http", semconv.HTTPTargetKey: "/", semconv.HTTPRequestContentLengthKey: "3", semconv.NetSockPeerAddrKey: hp[0], semconv.NetTransportKey: "ip_tcp", semconv.UserAgentOriginalKey: "Go-http-client/1.1", } client := ts.Client() ctx := context.Background() sc := trace.NewSpanContext(trace.SpanContextConfig{ TraceID: trace.TraceID{0x01}, SpanID: trace.SpanID{0x01}, }) ctx = trace.ContextWithRemoteSpanContext(ctx, sc) err := func(ctx context.Context) error { ctx, span := tr.Start(ctx, "test") defer span.End() bag, _ := baggage.Parse("foo=bar") ctx = baggage.ContextWithBaggage(ctx, bag) req, _ := http.NewRequest("GET", ts.URL, strings.NewReader("foo")) otelhttptrace.Inject(ctx, req, props) res, err := client.Do(req) if err != nil { t.Fatalf("Request failed: %s", err.Error()) } _ = res.Body.Close() return nil }(ctx) if err != nil { panic("unexpected error in http request: " + err.Error()) } } func TestSpecifyPropagators(t *testing.T) { tr := noop.NewTracerProvider().Tracer("httptrace/client") expectedCorrs := map[string]string{"foo": "bar"} // Mock http server ts := httptest.NewServer( http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { _, corrs, span := otelhttptrace.Extract(r.Context(), r, otelhttptrace.WithPropagators(propagation.Baggage{})) actualCorrs := make(map[string]string) for _, corr := range corrs.Members() { actualCorrs[corr.Key()] = corr.Value() } if diff := cmp.Diff(actualCorrs, expectedCorrs); diff != "" { t.Fatalf("[TestRoundtrip] Correlations are different: %v", diff) } if span.IsValid() { t.Fatalf("[TestRoundtrip] valid span extracted, expected none: %v", span) } _, err := w.Write([]byte("OK")) if err != nil { t.Fatal(err) } }), ) defer ts.Close() client := ts.Client() err := func(ctx context.Context) error { ctx, span := tr.Start(ctx, "test") defer span.End() bag, _ := baggage.Parse("foo=bar") ctx = baggage.ContextWithBaggage(ctx, bag) req, _ := http.NewRequest("GET", ts.URL, nil) otelhttptrace.Inject(ctx, req, otelhttptrace.WithPropagators(propagation.Baggage{})) res, err := client.Do(req) if err != nil { t.Fatalf("Request failed: %s", err.Error()) } _ = res.Body.Close() return nil }(context.Background()) if err != nil { panic("unexpected error in http request: " + err.Error()) } } internal/000077500000000000000000000000001470323427300356765ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/httptrace/otelhttptracesemconvutil/000077500000000000000000000000001470323427300402465ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/httptrace/otelhttptrace/internalgen.go000066400000000000000000000013631470323427300413510ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/httptrace/otelhttptrace/internal/semconvutil// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace/internal/semconvutil" // Generate semconvutil package: //go:generate gotmpl --body=../../../../../../../internal/shared/semconvutil/httpconv_test.go.tmpl "--data={}" --out=httpconv_test.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconvutil/httpconv.go.tmpl "--data={}" --out=httpconv.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconvutil/netconv_test.go.tmpl "--data={}" --out=netconv_test.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconvutil/netconv.go.tmpl "--data={}" --out=netconv.go httpconv.go000066400000000000000000000466201470323427300424520ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/httptrace/otelhttptrace/internal/semconvutil// Code created by gotmpl. DO NOT MODIFY. // source: internal/shared/semconvutil/httpconv.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace/internal/semconvutil" import ( "fmt" "net/http" "strings" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" semconv "go.opentelemetry.io/otel/semconv/v1.20.0" ) // HTTPClientResponse returns trace attributes for an HTTP response received by a // client from a server. It will return the following attributes if the related // values are defined in resp: "http.status.code", // "http.response_content_length". // // This does not add all OpenTelemetry required attributes for an HTTP event, // it assumes ClientRequest was used to create the span with a complete set of // attributes. If a complete set of attributes can be generated using the // request contained in resp. For example: // // append(HTTPClientResponse(resp), ClientRequest(resp.Request)...) func HTTPClientResponse(resp *http.Response) []attribute.KeyValue { return hc.ClientResponse(resp) } // HTTPClientRequest returns trace attributes for an HTTP request made by a client. // The following attributes are always returned: "http.url", "http.method", // "net.peer.name". The following attributes are returned if the related values // are defined in req: "net.peer.port", "user_agent.original", // "http.request_content_length". func HTTPClientRequest(req *http.Request) []attribute.KeyValue { return hc.ClientRequest(req) } // HTTPClientRequestMetrics returns metric attributes for an HTTP request made by a client. // The following attributes are always returned: "http.method", "net.peer.name". // The following attributes are returned if the // related values are defined in req: "net.peer.port". func HTTPClientRequestMetrics(req *http.Request) []attribute.KeyValue { return hc.ClientRequestMetrics(req) } // HTTPClientStatus returns a span status code and message for an HTTP status code // value received by a client. func HTTPClientStatus(code int) (codes.Code, string) { return hc.ClientStatus(code) } // HTTPServerRequest returns trace attributes for an HTTP request received by a // server. // // The server must be the primary server name if it is known. For example this // would be the ServerName directive // (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache // server, and the server_name directive // (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an // nginx server. More generically, the primary server name would be the host // header value that matches the default virtual host of an HTTP server. It // should include the host identifier and if a port is used to route to the // server that port identifier should be included as an appropriate port // suffix. // // If the primary server name is not known, server should be an empty string. // The req Host will be used to determine the server instead. // // The following attributes are always returned: "http.method", "http.scheme", // "http.target", "net.host.name". The following attributes are returned if // they related values are defined in req: "net.host.port", "net.sock.peer.addr", // "net.sock.peer.port", "user_agent.original", "http.client_ip". func HTTPServerRequest(server string, req *http.Request) []attribute.KeyValue { return hc.ServerRequest(server, req) } // HTTPServerRequestMetrics returns metric attributes for an HTTP request received by a // server. // // The server must be the primary server name if it is known. For example this // would be the ServerName directive // (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache // server, and the server_name directive // (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an // nginx server. More generically, the primary server name would be the host // header value that matches the default virtual host of an HTTP server. It // should include the host identifier and if a port is used to route to the // server that port identifier should be included as an appropriate port // suffix. // // If the primary server name is not known, server should be an empty string. // The req Host will be used to determine the server instead. // // The following attributes are always returned: "http.method", "http.scheme", // "net.host.name". The following attributes are returned if they related // values are defined in req: "net.host.port". func HTTPServerRequestMetrics(server string, req *http.Request) []attribute.KeyValue { return hc.ServerRequestMetrics(server, req) } // HTTPServerStatus returns a span status code and message for an HTTP status code // value returned by a server. Status codes in the 400-499 range are not // returned as errors. func HTTPServerStatus(code int) (codes.Code, string) { return hc.ServerStatus(code) } // httpConv are the HTTP semantic convention attributes defined for a version // of the OpenTelemetry specification. type httpConv struct { NetConv *netConv HTTPClientIPKey attribute.Key HTTPMethodKey attribute.Key HTTPRequestContentLengthKey attribute.Key HTTPResponseContentLengthKey attribute.Key HTTPRouteKey attribute.Key HTTPSchemeHTTP attribute.KeyValue HTTPSchemeHTTPS attribute.KeyValue HTTPStatusCodeKey attribute.Key HTTPTargetKey attribute.Key HTTPURLKey attribute.Key UserAgentOriginalKey attribute.Key } var hc = &httpConv{ NetConv: nc, HTTPClientIPKey: semconv.HTTPClientIPKey, HTTPMethodKey: semconv.HTTPMethodKey, HTTPRequestContentLengthKey: semconv.HTTPRequestContentLengthKey, HTTPResponseContentLengthKey: semconv.HTTPResponseContentLengthKey, HTTPRouteKey: semconv.HTTPRouteKey, HTTPSchemeHTTP: semconv.HTTPSchemeHTTP, HTTPSchemeHTTPS: semconv.HTTPSchemeHTTPS, HTTPStatusCodeKey: semconv.HTTPStatusCodeKey, HTTPTargetKey: semconv.HTTPTargetKey, HTTPURLKey: semconv.HTTPURLKey, UserAgentOriginalKey: semconv.UserAgentOriginalKey, } // ClientResponse returns attributes for an HTTP response received by a client // from a server. The following attributes are returned if the related values // are defined in resp: "http.status.code", "http.response_content_length". // // This does not add all OpenTelemetry required attributes for an HTTP event, // it assumes ClientRequest was used to create the span with a complete set of // attributes. If a complete set of attributes can be generated using the // request contained in resp. For example: // // append(ClientResponse(resp), ClientRequest(resp.Request)...) func (c *httpConv) ClientResponse(resp *http.Response) []attribute.KeyValue { /* The following semantic conventions are returned if present: http.status_code int http.response_content_length int */ var n int if resp.StatusCode > 0 { n++ } if resp.ContentLength > 0 { n++ } attrs := make([]attribute.KeyValue, 0, n) if resp.StatusCode > 0 { attrs = append(attrs, c.HTTPStatusCodeKey.Int(resp.StatusCode)) } if resp.ContentLength > 0 { attrs = append(attrs, c.HTTPResponseContentLengthKey.Int(int(resp.ContentLength))) } return attrs } // ClientRequest returns attributes for an HTTP request made by a client. The // following attributes are always returned: "http.url", "http.method", // "net.peer.name". The following attributes are returned if the related values // are defined in req: "net.peer.port", "user_agent.original", // "http.request_content_length", "user_agent.original". func (c *httpConv) ClientRequest(req *http.Request) []attribute.KeyValue { /* The following semantic conventions are returned if present: http.method string user_agent.original string http.url string net.peer.name string net.peer.port int http.request_content_length int */ /* The following semantic conventions are not returned: http.status_code This requires the response. See ClientResponse. http.response_content_length This requires the response. See ClientResponse. net.sock.family This requires the socket used. net.sock.peer.addr This requires the socket used. net.sock.peer.name This requires the socket used. net.sock.peer.port This requires the socket used. http.resend_count This is something outside of a single request. net.protocol.name The value is the Request is ignored, and the go client will always use "http". net.protocol.version The value in the Request is ignored, and the go client will always use 1.1 or 2.0. */ n := 3 // URL, peer name, proto, and method. var h string if req.URL != nil { h = req.URL.Host } peer, p := firstHostPort(h, req.Header.Get("Host")) port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", p) if port > 0 { n++ } useragent := req.UserAgent() if useragent != "" { n++ } if req.ContentLength > 0 { n++ } attrs := make([]attribute.KeyValue, 0, n) attrs = append(attrs, c.method(req.Method)) var u string if req.URL != nil { // Remove any username/password info that may be in the URL. userinfo := req.URL.User req.URL.User = nil u = req.URL.String() // Restore any username/password info that was removed. req.URL.User = userinfo } attrs = append(attrs, c.HTTPURLKey.String(u)) attrs = append(attrs, c.NetConv.PeerName(peer)) if port > 0 { attrs = append(attrs, c.NetConv.PeerPort(port)) } if useragent != "" { attrs = append(attrs, c.UserAgentOriginalKey.String(useragent)) } if l := req.ContentLength; l > 0 { attrs = append(attrs, c.HTTPRequestContentLengthKey.Int64(l)) } return attrs } // ClientRequestMetrics returns metric attributes for an HTTP request made by a client. The // following attributes are always returned: "http.method", "net.peer.name". // The following attributes are returned if the related values // are defined in req: "net.peer.port". func (c *httpConv) ClientRequestMetrics(req *http.Request) []attribute.KeyValue { /* The following semantic conventions are returned if present: http.method string net.peer.name string net.peer.port int */ n := 2 // method, peer name. var h string if req.URL != nil { h = req.URL.Host } peer, p := firstHostPort(h, req.Header.Get("Host")) port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", p) if port > 0 { n++ } attrs := make([]attribute.KeyValue, 0, n) attrs = append(attrs, c.method(req.Method), c.NetConv.PeerName(peer)) if port > 0 { attrs = append(attrs, c.NetConv.PeerPort(port)) } return attrs } // ServerRequest returns attributes for an HTTP request received by a server. // // The server must be the primary server name if it is known. For example this // would be the ServerName directive // (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache // server, and the server_name directive // (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an // nginx server. More generically, the primary server name would be the host // header value that matches the default virtual host of an HTTP server. It // should include the host identifier and if a port is used to route to the // server that port identifier should be included as an appropriate port // suffix. // // If the primary server name is not known, server should be an empty string. // The req Host will be used to determine the server instead. // // The following attributes are always returned: "http.method", "http.scheme", // "http.target", "net.host.name". The following attributes are returned if they // related values are defined in req: "net.host.port", "net.sock.peer.addr", // "net.sock.peer.port", "user_agent.original", "http.client_ip", // "net.protocol.name", "net.protocol.version". func (c *httpConv) ServerRequest(server string, req *http.Request) []attribute.KeyValue { /* The following semantic conventions are returned if present: http.method string http.scheme string net.host.name string net.host.port int net.sock.peer.addr string net.sock.peer.port int user_agent.original string http.client_ip string net.protocol.name string Note: not set if the value is "http". net.protocol.version string http.target string Note: doesn't include the query parameter. */ /* The following semantic conventions are not returned: http.status_code This requires the response. http.request_content_length This requires the len() of body, which can mutate it. http.response_content_length This requires the response. http.route This is not available. net.sock.peer.name This would require a DNS lookup. net.sock.host.addr The request doesn't have access to the underlying socket. net.sock.host.port The request doesn't have access to the underlying socket. */ n := 4 // Method, scheme, proto, and host name. var host string var p int if server == "" { host, p = splitHostPort(req.Host) } else { // Prioritize the primary server name. host, p = splitHostPort(server) if p < 0 { _, p = splitHostPort(req.Host) } } hostPort := requiredHTTPPort(req.TLS != nil, p) if hostPort > 0 { n++ } peer, peerPort := splitHostPort(req.RemoteAddr) if peer != "" { n++ if peerPort > 0 { n++ } } useragent := req.UserAgent() if useragent != "" { n++ } clientIP := serverClientIP(req.Header.Get("X-Forwarded-For")) if clientIP != "" { n++ } var target string if req.URL != nil { target = req.URL.Path if target != "" { n++ } } protoName, protoVersion := netProtocol(req.Proto) if protoName != "" && protoName != "http" { n++ } if protoVersion != "" { n++ } attrs := make([]attribute.KeyValue, 0, n) attrs = append(attrs, c.method(req.Method)) attrs = append(attrs, c.scheme(req.TLS != nil)) attrs = append(attrs, c.NetConv.HostName(host)) if hostPort > 0 { attrs = append(attrs, c.NetConv.HostPort(hostPort)) } if peer != "" { // The Go HTTP server sets RemoteAddr to "IP:port", this will not be a // file-path that would be interpreted with a sock family. attrs = append(attrs, c.NetConv.SockPeerAddr(peer)) if peerPort > 0 { attrs = append(attrs, c.NetConv.SockPeerPort(peerPort)) } } if useragent != "" { attrs = append(attrs, c.UserAgentOriginalKey.String(useragent)) } if clientIP != "" { attrs = append(attrs, c.HTTPClientIPKey.String(clientIP)) } if target != "" { attrs = append(attrs, c.HTTPTargetKey.String(target)) } if protoName != "" && protoName != "http" { attrs = append(attrs, c.NetConv.NetProtocolName.String(protoName)) } if protoVersion != "" { attrs = append(attrs, c.NetConv.NetProtocolVersion.String(protoVersion)) } return attrs } // ServerRequestMetrics returns metric attributes for an HTTP request received // by a server. // // The server must be the primary server name if it is known. For example this // would be the ServerName directive // (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache // server, and the server_name directive // (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an // nginx server. More generically, the primary server name would be the host // header value that matches the default virtual host of an HTTP server. It // should include the host identifier and if a port is used to route to the // server that port identifier should be included as an appropriate port // suffix. // // If the primary server name is not known, server should be an empty string. // The req Host will be used to determine the server instead. // // The following attributes are always returned: "http.method", "http.scheme", // "net.host.name". The following attributes are returned if they related // values are defined in req: "net.host.port". func (c *httpConv) ServerRequestMetrics(server string, req *http.Request) []attribute.KeyValue { /* The following semantic conventions are returned if present: http.scheme string http.route string http.method string http.status_code int net.host.name string net.host.port int net.protocol.name string Note: not set if the value is "http". net.protocol.version string */ n := 3 // Method, scheme, and host name. var host string var p int if server == "" { host, p = splitHostPort(req.Host) } else { // Prioritize the primary server name. host, p = splitHostPort(server) if p < 0 { _, p = splitHostPort(req.Host) } } hostPort := requiredHTTPPort(req.TLS != nil, p) if hostPort > 0 { n++ } protoName, protoVersion := netProtocol(req.Proto) if protoName != "" { n++ } if protoVersion != "" { n++ } attrs := make([]attribute.KeyValue, 0, n) attrs = append(attrs, c.methodMetric(req.Method)) attrs = append(attrs, c.scheme(req.TLS != nil)) attrs = append(attrs, c.NetConv.HostName(host)) if hostPort > 0 { attrs = append(attrs, c.NetConv.HostPort(hostPort)) } if protoName != "" { attrs = append(attrs, c.NetConv.NetProtocolName.String(protoName)) } if protoVersion != "" { attrs = append(attrs, c.NetConv.NetProtocolVersion.String(protoVersion)) } return attrs } func (c *httpConv) method(method string) attribute.KeyValue { if method == "" { return c.HTTPMethodKey.String(http.MethodGet) } return c.HTTPMethodKey.String(method) } func (c *httpConv) methodMetric(method string) attribute.KeyValue { method = strings.ToUpper(method) switch method { case http.MethodConnect, http.MethodDelete, http.MethodGet, http.MethodHead, http.MethodOptions, http.MethodPatch, http.MethodPost, http.MethodPut, http.MethodTrace: default: method = "_OTHER" } return c.HTTPMethodKey.String(method) } func (c *httpConv) scheme(https bool) attribute.KeyValue { // nolint:revive if https { return c.HTTPSchemeHTTPS } return c.HTTPSchemeHTTP } func serverClientIP(xForwardedFor string) string { if idx := strings.Index(xForwardedFor, ","); idx >= 0 { xForwardedFor = xForwardedFor[:idx] } return xForwardedFor } func requiredHTTPPort(https bool, port int) int { // nolint:revive if https { if port > 0 && port != 443 { return port } } else { if port > 0 && port != 80 { return port } } return -1 } // Return the request host and port from the first non-empty source. func firstHostPort(source ...string) (host string, port int) { for _, hostport := range source { host, port = splitHostPort(hostport) if host != "" || port > 0 { break } } return } // ClientStatus returns a span status code and message for an HTTP status code // value received by a client. func (c *httpConv) ClientStatus(code int) (codes.Code, string) { if code < 100 || code >= 600 { return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) } if code >= 400 { return codes.Error, "" } return codes.Unset, "" } // ServerStatus returns a span status code and message for an HTTP status code // value returned by a server. Status codes in the 400-499 range are not // returned as errors. func (c *httpConv) ServerStatus(code int) (codes.Code, string) { if code < 100 || code >= 600 { return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) } if code >= 500 { return codes.Error, "" } return codes.Unset, "" } httpconv_test.go000066400000000000000000000372171470323427300435130ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/httptrace/otelhttptrace/internal/semconvutil// Code created by gotmpl. DO NOT MODIFY. // source: internal/shared/semconvutil/httpconv_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconvutil import ( "net/http" "net/http/httptest" "net/url" "strconv" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" ) func TestHTTPClientResponse(t *testing.T) { const stat, n = 201, 397 resp := &http.Response{ StatusCode: stat, ContentLength: n, } got := HTTPClientResponse(resp) assert.Equal(t, 2, cap(got), "slice capacity") assert.ElementsMatch(t, []attribute.KeyValue{ attribute.Key("http.status_code").Int(stat), attribute.Key("http.response_content_length").Int(n), }, got) } func TestHTTPSClientRequest(t *testing.T) { req := &http.Request{ Method: http.MethodGet, URL: &url.URL{ Scheme: "https", Host: "127.0.0.1:443", Path: "/resource", }, Proto: "HTTP/1.0", ProtoMajor: 1, ProtoMinor: 0, } assert.ElementsMatch( t, []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.url", "https://127.0.0.1:443/resource"), attribute.String("net.peer.name", "127.0.0.1"), }, HTTPClientRequest(req), ) } func TestHTTPSClientRequestMetrics(t *testing.T) { req := &http.Request{ Method: http.MethodGet, URL: &url.URL{ Scheme: "https", Host: "127.0.0.1:443", Path: "/resource", }, Proto: "HTTP/1.0", ProtoMajor: 1, ProtoMinor: 0, } assert.ElementsMatch( t, []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("net.peer.name", "127.0.0.1"), }, HTTPClientRequestMetrics(req), ) } func TestHTTPClientRequest(t *testing.T) { const ( user = "alice" n = 128 agent = "Go-http-client/1.1" ) req := &http.Request{ Method: http.MethodGet, URL: &url.URL{ Scheme: "http", Host: "127.0.0.1:8080", Path: "/resource", }, Proto: "HTTP/1.0", ProtoMajor: 1, ProtoMinor: 0, Header: http.Header{ "User-Agent": []string{agent}, }, ContentLength: n, } req.SetBasicAuth(user, "pswrd") assert.ElementsMatch( t, []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.url", "http://127.0.0.1:8080/resource"), attribute.String("net.peer.name", "127.0.0.1"), attribute.Int("net.peer.port", 8080), attribute.String("user_agent.original", agent), attribute.Int("http.request_content_length", n), }, HTTPClientRequest(req), ) } func TestHTTPClientRequestMetrics(t *testing.T) { const ( user = "alice" n = 128 agent = "Go-http-client/1.1" ) req := &http.Request{ Method: http.MethodGet, URL: &url.URL{ Scheme: "http", Host: "127.0.0.1:8080", Path: "/resource", }, Proto: "HTTP/1.0", ProtoMajor: 1, ProtoMinor: 0, Header: http.Header{ "User-Agent": []string{agent}, }, ContentLength: n, } req.SetBasicAuth(user, "pswrd") assert.ElementsMatch( t, []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("net.peer.name", "127.0.0.1"), attribute.Int("net.peer.port", 8080), }, HTTPClientRequestMetrics(req), ) } func TestHTTPClientRequestRequired(t *testing.T) { req := new(http.Request) var got []attribute.KeyValue assert.NotPanics(t, func() { got = HTTPClientRequest(req) }) want := []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.url", ""), attribute.String("net.peer.name", ""), } assert.Equal(t, want, got) } func TestHTTPServerRequest(t *testing.T) { got := make(chan *http.Request, 1) handler := func(w http.ResponseWriter, r *http.Request) { got <- r w.WriteHeader(http.StatusOK) } srv := httptest.NewServer(http.HandlerFunc(handler)) defer srv.Close() srvURL, err := url.Parse(srv.URL) require.NoError(t, err) srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) require.NoError(t, err) resp, err := srv.Client().Get(srv.URL) require.NoError(t, err) require.NoError(t, resp.Body.Close()) req := <-got peer, peerPort := splitHostPort(req.RemoteAddr) const user = "alice" req.SetBasicAuth(user, "pswrd") const clientIP = "127.0.0.5" req.Header.Add("X-Forwarded-For", clientIP) assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.scheme", "http"), attribute.String("net.host.name", srvURL.Hostname()), attribute.Int("net.host.port", int(srvPort)), attribute.String("net.sock.peer.addr", peer), attribute.Int("net.sock.peer.port", peerPort), attribute.String("user_agent.original", "Go-http-client/1.1"), attribute.String("http.client_ip", clientIP), attribute.String("net.protocol.version", "1.1"), attribute.String("http.target", "/"), }, HTTPServerRequest("", req)) } func TestHTTPServerRequestMetrics(t *testing.T) { got := make(chan *http.Request, 1) handler := func(w http.ResponseWriter, r *http.Request) { got <- r w.WriteHeader(http.StatusOK) } srv := httptest.NewServer(http.HandlerFunc(handler)) defer srv.Close() srvURL, err := url.Parse(srv.URL) require.NoError(t, err) srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) require.NoError(t, err) resp, err := srv.Client().Get(srv.URL) require.NoError(t, err) require.NoError(t, resp.Body.Close()) req := <-got assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.scheme", "http"), attribute.String("net.host.name", srvURL.Hostname()), attribute.Int("net.host.port", int(srvPort)), attribute.String("net.protocol.name", "http"), attribute.String("net.protocol.version", "1.1"), }, HTTPServerRequestMetrics("", req)) } func TestHTTPServerName(t *testing.T) { req := new(http.Request) var got []attribute.KeyValue const ( host = "test.semconv.server" port = 8080 ) portStr := strconv.Itoa(port) server := host + ":" + portStr assert.NotPanics(t, func() { got = HTTPServerRequest(server, req) }) assert.Contains(t, got, attribute.String("net.host.name", host)) assert.Contains(t, got, attribute.Int("net.host.port", port)) req = &http.Request{Host: "alt.host.name:" + portStr} // The server parameter does not include a port, ServerRequest should use // the port in the request Host field. assert.NotPanics(t, func() { got = HTTPServerRequest(host, req) }) assert.Contains(t, got, attribute.String("net.host.name", host)) assert.Contains(t, got, attribute.Int("net.host.port", port)) } func TestHTTPServerRequestFailsGracefully(t *testing.T) { req := new(http.Request) var got []attribute.KeyValue assert.NotPanics(t, func() { got = HTTPServerRequest("", req) }) want := []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.scheme", "http"), attribute.String("net.host.name", ""), } assert.ElementsMatch(t, want, got) } func TestHTTPMethod(t *testing.T) { assert.Equal(t, attribute.String("http.method", "POST"), hc.method("POST")) assert.Equal(t, attribute.String("http.method", "GET"), hc.method("")) assert.Equal(t, attribute.String("http.method", "garbage"), hc.method("garbage")) } func TestHTTPScheme(t *testing.T) { assert.Equal(t, attribute.String("http.scheme", "http"), hc.scheme(false)) assert.Equal(t, attribute.String("http.scheme", "https"), hc.scheme(true)) } func TestHTTPServerClientIP(t *testing.T) { tests := []struct { xForwardedFor string want string }{ {"", ""}, {"127.0.0.1", "127.0.0.1"}, {"127.0.0.1,127.0.0.5", "127.0.0.1"}, } for _, test := range tests { got := serverClientIP(test.xForwardedFor) assert.Equal(t, test.want, got, test.xForwardedFor) } } func TestRequiredHTTPPort(t *testing.T) { tests := []struct { https bool port int want int }{ {true, 443, -1}, {true, 80, 80}, {true, 8081, 8081}, {false, 443, 443}, {false, 80, -1}, {false, 8080, 8080}, } for _, test := range tests { got := requiredHTTPPort(test.https, test.port) assert.Equal(t, test.want, got, test.https, test.port) } } func TestFirstHostPort(t *testing.T) { host, port := "127.0.0.1", 8080 hostport := "127.0.0.1:8080" sources := [][]string{ {hostport}, {"", hostport}, {"", "", hostport}, {"", "", hostport, ""}, {"", "", hostport, "127.0.0.3:80"}, } for _, src := range sources { h, p := firstHostPort(src...) assert.Equal(t, host, h, src) assert.Equal(t, port, p, src) } } func TestHTTPClientStatus(t *testing.T) { tests := []struct { code int stat codes.Code msg bool }{ {0, codes.Error, true}, {http.StatusContinue, codes.Unset, false}, {http.StatusSwitchingProtocols, codes.Unset, false}, {http.StatusProcessing, codes.Unset, false}, {http.StatusEarlyHints, codes.Unset, false}, {http.StatusOK, codes.Unset, false}, {http.StatusCreated, codes.Unset, false}, {http.StatusAccepted, codes.Unset, false}, {http.StatusNonAuthoritativeInfo, codes.Unset, false}, {http.StatusNoContent, codes.Unset, false}, {http.StatusResetContent, codes.Unset, false}, {http.StatusPartialContent, codes.Unset, false}, {http.StatusMultiStatus, codes.Unset, false}, {http.StatusAlreadyReported, codes.Unset, false}, {http.StatusIMUsed, codes.Unset, false}, {http.StatusMultipleChoices, codes.Unset, false}, {http.StatusMovedPermanently, codes.Unset, false}, {http.StatusFound, codes.Unset, false}, {http.StatusSeeOther, codes.Unset, false}, {http.StatusNotModified, codes.Unset, false}, {http.StatusUseProxy, codes.Unset, false}, {306, codes.Unset, false}, {http.StatusTemporaryRedirect, codes.Unset, false}, {http.StatusPermanentRedirect, codes.Unset, false}, {http.StatusBadRequest, codes.Error, false}, {http.StatusUnauthorized, codes.Error, false}, {http.StatusPaymentRequired, codes.Error, false}, {http.StatusForbidden, codes.Error, false}, {http.StatusNotFound, codes.Error, false}, {http.StatusMethodNotAllowed, codes.Error, false}, {http.StatusNotAcceptable, codes.Error, false}, {http.StatusProxyAuthRequired, codes.Error, false}, {http.StatusRequestTimeout, codes.Error, false}, {http.StatusConflict, codes.Error, false}, {http.StatusGone, codes.Error, false}, {http.StatusLengthRequired, codes.Error, false}, {http.StatusPreconditionFailed, codes.Error, false}, {http.StatusRequestEntityTooLarge, codes.Error, false}, {http.StatusRequestURITooLong, codes.Error, false}, {http.StatusUnsupportedMediaType, codes.Error, false}, {http.StatusRequestedRangeNotSatisfiable, codes.Error, false}, {http.StatusExpectationFailed, codes.Error, false}, {http.StatusTeapot, codes.Error, false}, {http.StatusMisdirectedRequest, codes.Error, false}, {http.StatusUnprocessableEntity, codes.Error, false}, {http.StatusLocked, codes.Error, false}, {http.StatusFailedDependency, codes.Error, false}, {http.StatusTooEarly, codes.Error, false}, {http.StatusUpgradeRequired, codes.Error, false}, {http.StatusPreconditionRequired, codes.Error, false}, {http.StatusTooManyRequests, codes.Error, false}, {http.StatusRequestHeaderFieldsTooLarge, codes.Error, false}, {http.StatusUnavailableForLegalReasons, codes.Error, false}, {499, codes.Error, false}, {http.StatusInternalServerError, codes.Error, false}, {http.StatusNotImplemented, codes.Error, false}, {http.StatusBadGateway, codes.Error, false}, {http.StatusServiceUnavailable, codes.Error, false}, {http.StatusGatewayTimeout, codes.Error, false}, {http.StatusHTTPVersionNotSupported, codes.Error, false}, {http.StatusVariantAlsoNegotiates, codes.Error, false}, {http.StatusInsufficientStorage, codes.Error, false}, {http.StatusLoopDetected, codes.Error, false}, {http.StatusNotExtended, codes.Error, false}, {http.StatusNetworkAuthenticationRequired, codes.Error, false}, {600, codes.Error, true}, } for _, test := range tests { t.Run(strconv.Itoa(test.code), func(t *testing.T) { c, msg := HTTPClientStatus(test.code) assert.Equal(t, test.stat, c) if test.msg && msg == "" { t.Errorf("expected non-empty message for %d", test.code) } else if !test.msg && msg != "" { t.Errorf("expected empty message for %d, got: %s", test.code, msg) } }) } } func TestHTTPServerStatus(t *testing.T) { tests := []struct { code int stat codes.Code msg bool }{ {0, codes.Error, true}, {http.StatusContinue, codes.Unset, false}, {http.StatusSwitchingProtocols, codes.Unset, false}, {http.StatusProcessing, codes.Unset, false}, {http.StatusEarlyHints, codes.Unset, false}, {http.StatusOK, codes.Unset, false}, {http.StatusCreated, codes.Unset, false}, {http.StatusAccepted, codes.Unset, false}, {http.StatusNonAuthoritativeInfo, codes.Unset, false}, {http.StatusNoContent, codes.Unset, false}, {http.StatusResetContent, codes.Unset, false}, {http.StatusPartialContent, codes.Unset, false}, {http.StatusMultiStatus, codes.Unset, false}, {http.StatusAlreadyReported, codes.Unset, false}, {http.StatusIMUsed, codes.Unset, false}, {http.StatusMultipleChoices, codes.Unset, false}, {http.StatusMovedPermanently, codes.Unset, false}, {http.StatusFound, codes.Unset, false}, {http.StatusSeeOther, codes.Unset, false}, {http.StatusNotModified, codes.Unset, false}, {http.StatusUseProxy, codes.Unset, false}, {306, codes.Unset, false}, {http.StatusTemporaryRedirect, codes.Unset, false}, {http.StatusPermanentRedirect, codes.Unset, false}, {http.StatusBadRequest, codes.Unset, false}, {http.StatusUnauthorized, codes.Unset, false}, {http.StatusPaymentRequired, codes.Unset, false}, {http.StatusForbidden, codes.Unset, false}, {http.StatusNotFound, codes.Unset, false}, {http.StatusMethodNotAllowed, codes.Unset, false}, {http.StatusNotAcceptable, codes.Unset, false}, {http.StatusProxyAuthRequired, codes.Unset, false}, {http.StatusRequestTimeout, codes.Unset, false}, {http.StatusConflict, codes.Unset, false}, {http.StatusGone, codes.Unset, false}, {http.StatusLengthRequired, codes.Unset, false}, {http.StatusPreconditionFailed, codes.Unset, false}, {http.StatusRequestEntityTooLarge, codes.Unset, false}, {http.StatusRequestURITooLong, codes.Unset, false}, {http.StatusUnsupportedMediaType, codes.Unset, false}, {http.StatusRequestedRangeNotSatisfiable, codes.Unset, false}, {http.StatusExpectationFailed, codes.Unset, false}, {http.StatusTeapot, codes.Unset, false}, {http.StatusMisdirectedRequest, codes.Unset, false}, {http.StatusUnprocessableEntity, codes.Unset, false}, {http.StatusLocked, codes.Unset, false}, {http.StatusFailedDependency, codes.Unset, false}, {http.StatusTooEarly, codes.Unset, false}, {http.StatusUpgradeRequired, codes.Unset, false}, {http.StatusPreconditionRequired, codes.Unset, false}, {http.StatusTooManyRequests, codes.Unset, false}, {http.StatusRequestHeaderFieldsTooLarge, codes.Unset, false}, {http.StatusUnavailableForLegalReasons, codes.Unset, false}, {499, codes.Unset, false}, {http.StatusInternalServerError, codes.Error, false}, {http.StatusNotImplemented, codes.Error, false}, {http.StatusBadGateway, codes.Error, false}, {http.StatusServiceUnavailable, codes.Error, false}, {http.StatusGatewayTimeout, codes.Error, false}, {http.StatusHTTPVersionNotSupported, codes.Error, false}, {http.StatusVariantAlsoNegotiates, codes.Error, false}, {http.StatusInsufficientStorage, codes.Error, false}, {http.StatusLoopDetected, codes.Error, false}, {http.StatusNotExtended, codes.Error, false}, {http.StatusNetworkAuthenticationRequired, codes.Error, false}, {600, codes.Error, true}, } for _, test := range tests { c, msg := HTTPServerStatus(test.code) assert.Equal(t, test.stat, c) if test.msg && msg == "" { t.Errorf("expected non-empty message for %d", test.code) } else if !test.msg && msg != "" { t.Errorf("expected empty message for %d, got: %s", test.code, msg) } } } netconv.go000066400000000000000000000122661470323427300422600ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/httptrace/otelhttptrace/internal/semconvutil// Code created by gotmpl. DO NOT MODIFY. // source: internal/shared/semconvutil/netconv.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace/internal/semconvutil" import ( "net" "strconv" "strings" "go.opentelemetry.io/otel/attribute" semconv "go.opentelemetry.io/otel/semconv/v1.20.0" ) // NetTransport returns a trace attribute describing the transport protocol of the // passed network. See the net.Dial for information about acceptable network // values. func NetTransport(network string) attribute.KeyValue { return nc.Transport(network) } // netConv are the network semantic convention attributes defined for a version // of the OpenTelemetry specification. type netConv struct { NetHostNameKey attribute.Key NetHostPortKey attribute.Key NetPeerNameKey attribute.Key NetPeerPortKey attribute.Key NetProtocolName attribute.Key NetProtocolVersion attribute.Key NetSockFamilyKey attribute.Key NetSockPeerAddrKey attribute.Key NetSockPeerPortKey attribute.Key NetSockHostAddrKey attribute.Key NetSockHostPortKey attribute.Key NetTransportOther attribute.KeyValue NetTransportTCP attribute.KeyValue NetTransportUDP attribute.KeyValue NetTransportInProc attribute.KeyValue } var nc = &netConv{ NetHostNameKey: semconv.NetHostNameKey, NetHostPortKey: semconv.NetHostPortKey, NetPeerNameKey: semconv.NetPeerNameKey, NetPeerPortKey: semconv.NetPeerPortKey, NetProtocolName: semconv.NetProtocolNameKey, NetProtocolVersion: semconv.NetProtocolVersionKey, NetSockFamilyKey: semconv.NetSockFamilyKey, NetSockPeerAddrKey: semconv.NetSockPeerAddrKey, NetSockPeerPortKey: semconv.NetSockPeerPortKey, NetSockHostAddrKey: semconv.NetSockHostAddrKey, NetSockHostPortKey: semconv.NetSockHostPortKey, NetTransportOther: semconv.NetTransportOther, NetTransportTCP: semconv.NetTransportTCP, NetTransportUDP: semconv.NetTransportUDP, NetTransportInProc: semconv.NetTransportInProc, } func (c *netConv) Transport(network string) attribute.KeyValue { switch network { case "tcp", "tcp4", "tcp6": return c.NetTransportTCP case "udp", "udp4", "udp6": return c.NetTransportUDP case "unix", "unixgram", "unixpacket": return c.NetTransportInProc default: // "ip:*", "ip4:*", and "ip6:*" all are considered other. return c.NetTransportOther } } // Host returns attributes for a network host address. func (c *netConv) Host(address string) []attribute.KeyValue { h, p := splitHostPort(address) var n int if h != "" { n++ if p > 0 { n++ } } if n == 0 { return nil } attrs := make([]attribute.KeyValue, 0, n) attrs = append(attrs, c.HostName(h)) if p > 0 { attrs = append(attrs, c.HostPort(p)) } return attrs } func (c *netConv) HostName(name string) attribute.KeyValue { return c.NetHostNameKey.String(name) } func (c *netConv) HostPort(port int) attribute.KeyValue { return c.NetHostPortKey.Int(port) } func family(network, address string) string { switch network { case "unix", "unixgram", "unixpacket": return "unix" default: if ip := net.ParseIP(address); ip != nil { if ip.To4() == nil { return "inet6" } return "inet" } } return "" } // Peer returns attributes for a network peer address. func (c *netConv) Peer(address string) []attribute.KeyValue { h, p := splitHostPort(address) var n int if h != "" { n++ if p > 0 { n++ } } if n == 0 { return nil } attrs := make([]attribute.KeyValue, 0, n) attrs = append(attrs, c.PeerName(h)) if p > 0 { attrs = append(attrs, c.PeerPort(p)) } return attrs } func (c *netConv) PeerName(name string) attribute.KeyValue { return c.NetPeerNameKey.String(name) } func (c *netConv) PeerPort(port int) attribute.KeyValue { return c.NetPeerPortKey.Int(port) } func (c *netConv) SockPeerAddr(addr string) attribute.KeyValue { return c.NetSockPeerAddrKey.String(addr) } func (c *netConv) SockPeerPort(port int) attribute.KeyValue { return c.NetSockPeerPortKey.Int(port) } // splitHostPort splits a network address hostport of the form "host", // "host%zone", "[host]", "[host%zone], "host:port", "host%zone:port", // "[host]:port", "[host%zone]:port", or ":port" into host or host%zone and // port. // // An empty host is returned if it is not provided or unparsable. A negative // port is returned if it is not provided or unparsable. func splitHostPort(hostport string) (host string, port int) { port = -1 if strings.HasPrefix(hostport, "[") { addrEnd := strings.LastIndex(hostport, "]") if addrEnd < 0 { // Invalid hostport. return } if i := strings.LastIndex(hostport[addrEnd:], ":"); i < 0 { host = hostport[1:addrEnd] return } } else { if i := strings.LastIndex(hostport, ":"); i < 0 { host = hostport return } } host, pStr, err := net.SplitHostPort(hostport) if err != nil { return } p, err := strconv.ParseUint(pStr, 10, 16) if err != nil { return } return host, int(p) // nolint: gosec // Bitsize checked to be 16 above. } func netProtocol(proto string) (name string, version string) { name, version, _ = strings.Cut(proto, "/") name = strings.ToLower(name) return name, version } netconv_test.go000066400000000000000000000130111470323427300433040ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/httptrace/otelhttptrace/internal/semconvutil// Code created by gotmpl. DO NOT MODIFY. // source: internal/shared/semconvutil/netconv_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconvutil import ( "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/attribute" ) const ( addr = "127.0.0.1" port = 1834 ) func TestNetTransport(t *testing.T) { transports := map[string]attribute.KeyValue{ "tcp": attribute.String("net.transport", "ip_tcp"), "tcp4": attribute.String("net.transport", "ip_tcp"), "tcp6": attribute.String("net.transport", "ip_tcp"), "udp": attribute.String("net.transport", "ip_udp"), "udp4": attribute.String("net.transport", "ip_udp"), "udp6": attribute.String("net.transport", "ip_udp"), "unix": attribute.String("net.transport", "inproc"), "unixgram": attribute.String("net.transport", "inproc"), "unixpacket": attribute.String("net.transport", "inproc"), "ip:1": attribute.String("net.transport", "other"), "ip:icmp": attribute.String("net.transport", "other"), "ip4:proto": attribute.String("net.transport", "other"), "ip6:proto": attribute.String("net.transport", "other"), } for network, want := range transports { assert.Equal(t, want, NetTransport(network)) } } func TestNetHost(t *testing.T) { testAddrs(t, []addrTest{ {address: "", expected: nil}, {address: "192.0.0.1", expected: []attribute.KeyValue{ nc.HostName("192.0.0.1"), }}, {address: "192.0.0.1:9090", expected: []attribute.KeyValue{ nc.HostName("192.0.0.1"), nc.HostPort(9090), }}, }, nc.Host) } func TestNetHostName(t *testing.T) { expected := attribute.Key("net.host.name").String(addr) assert.Equal(t, expected, nc.HostName(addr)) } func TestNetHostPort(t *testing.T) { expected := attribute.Key("net.host.port").Int(port) assert.Equal(t, expected, nc.HostPort(port)) } func TestNetPeer(t *testing.T) { testAddrs(t, []addrTest{ {address: "", expected: nil}, {address: "example.com", expected: []attribute.KeyValue{ nc.PeerName("example.com"), }}, {address: "/tmp/file", expected: []attribute.KeyValue{ nc.PeerName("/tmp/file"), }}, {address: "192.0.0.1", expected: []attribute.KeyValue{ nc.PeerName("192.0.0.1"), }}, {address: ":9090", expected: nil}, {address: "192.0.0.1:9090", expected: []attribute.KeyValue{ nc.PeerName("192.0.0.1"), nc.PeerPort(9090), }}, }, nc.Peer) } func TestNetPeerName(t *testing.T) { expected := attribute.Key("net.peer.name").String(addr) assert.Equal(t, expected, nc.PeerName(addr)) } func TestNetPeerPort(t *testing.T) { expected := attribute.Key("net.peer.port").Int(port) assert.Equal(t, expected, nc.PeerPort(port)) } func TestNetSockPeerName(t *testing.T) { expected := attribute.Key("net.sock.peer.addr").String(addr) assert.Equal(t, expected, nc.SockPeerAddr(addr)) } func TestNetSockPeerPort(t *testing.T) { expected := attribute.Key("net.sock.peer.port").Int(port) assert.Equal(t, expected, nc.SockPeerPort(port)) } func TestNetFamily(t *testing.T) { tests := []struct { network string address string expect string }{ {"", "", ""}, {"unix", "", "unix"}, {"unix", "gibberish", "unix"}, {"unixgram", "", "unix"}, {"unixgram", "gibberish", "unix"}, {"unixpacket", "gibberish", "unix"}, {"tcp", "123.0.2.8", "inet"}, {"tcp", "gibberish", ""}, {"", "123.0.2.8", "inet"}, {"", "gibberish", ""}, {"tcp", "fe80::1", "inet6"}, {"", "fe80::1", "inet6"}, } for _, test := range tests { got := family(test.network, test.address) assert.Equal(t, test.expect, got, test.network+"/"+test.address) } } func TestSplitHostPort(t *testing.T) { tests := []struct { hostport string host string port int }{ {"", "", -1}, {":8080", "", 8080}, {"127.0.0.1", "127.0.0.1", -1}, {"www.example.com", "www.example.com", -1}, {"127.0.0.1%25en0", "127.0.0.1%25en0", -1}, {"[]", "", -1}, // Ensure this doesn't panic. {"[fe80::1", "", -1}, {"[fe80::1]", "fe80::1", -1}, {"[fe80::1%25en0]", "fe80::1%25en0", -1}, {"[fe80::1]:8080", "fe80::1", 8080}, {"[fe80::1]::", "", -1}, // Too many colons. {"127.0.0.1:", "127.0.0.1", -1}, {"127.0.0.1:port", "127.0.0.1", -1}, {"127.0.0.1:8080", "127.0.0.1", 8080}, {"www.example.com:8080", "www.example.com", 8080}, {"127.0.0.1%25en0:8080", "127.0.0.1%25en0", 8080}, } for _, test := range tests { h, p := splitHostPort(test.hostport) assert.Equal(t, test.host, h, test.hostport) assert.Equal(t, test.port, p, test.hostport) } } type addrTest struct { address string expected []attribute.KeyValue } func testAddrs(t *testing.T, tests []addrTest, f func(string) []attribute.KeyValue) { t.Helper() for _, test := range tests { got := f(test.address) assert.Equal(t, cap(test.expected), cap(got), "slice capacity") assert.ElementsMatch(t, test.expected, got, test.address) } } func TestNetProtocol(t *testing.T) { type testCase struct { name, version string } tests := map[string]testCase{ "HTTP/1.0": {name: "http", version: "1.0"}, "HTTP/1.1": {name: "http", version: "1.1"}, "HTTP/2": {name: "http", version: "2"}, "HTTP/3": {name: "http", version: "3"}, "SPDY": {name: "spdy"}, "SPDY/2": {name: "spdy", version: "2"}, "QUIC": {name: "quic"}, "unknown/proto/2": {name: "unknown", version: "proto/2"}, "other": {name: "other"}, } for proto, want := range tests { name, version := netProtocol(proto) assert.Equal(t, want.name, name) assert.Equal(t, want.version, version) } } test/000077500000000000000000000000001470323427300350415ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/httptrace/otelhttptraceclienttrace_test.go000066400000000000000000000373341470323427300407360ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/httptrace/otelhttptrace/test// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package test import ( "bytes" "context" "net/http" "net/http/httptest" "net/http/httptrace" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/sdk/trace/tracetest" ) func getSpanFromRecorder(sr *tracetest.SpanRecorder, name string) (trace.ReadOnlySpan, bool) { for _, s := range sr.Ended() { if s.Name() == name { return s, true } } return nil, false } func getSpansFromRecorder(sr *tracetest.SpanRecorder, name string) []trace.ReadOnlySpan { var ret []trace.ReadOnlySpan for _, s := range sr.Ended() { if s.Name() == name { ret = append(ret, s) } } return ret } func TestHTTPRequestWithClientTrace(t *testing.T) { sr := tracetest.NewSpanRecorder() tp := trace.NewTracerProvider(trace.WithSpanProcessor(sr)) otel.SetTracerProvider(tp) tr := tp.Tracer("httptrace/client") // Mock http server ts := httptest.NewServer( http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { }), ) defer ts.Close() address := ts.Listener.Addr() client := ts.Client() err := func(ctx context.Context) error { ctx, span := tr.Start(ctx, "test") defer span.End() req, _ := http.NewRequest("GET", ts.URL, nil) _, req = otelhttptrace.W3C(ctx, req) res, err := client.Do(req) if err != nil { t.Fatalf("Request failed: %s", err.Error()) } _ = res.Body.Close() return nil }(context.Background()) if err != nil { panic("unexpected error in http request: " + err.Error()) } testLen := []struct { name string attributes []attribute.KeyValue parent string }{ { name: "http.connect", attributes: []attribute.KeyValue{ attribute.Key("http.conn.done.addr").String(address.String()), attribute.Key("http.conn.done.network").String("tcp"), attribute.Key("http.conn.start.network").String("tcp"), attribute.Key("http.remote").String(address.String()), }, parent: "http.getconn", }, { name: "http.getconn", attributes: []attribute.KeyValue{ attribute.Key("http.remote").String(address.String()), attribute.Key("net.host.name").String(address.String()), attribute.Key("http.conn.reused").Bool(false), attribute.Key("http.conn.wasidle").Bool(false), }, parent: "test", }, { name: "http.receive", parent: "test", }, { name: "http.headers", parent: "test", }, { name: "http.send", parent: "test", }, { name: "test", }, } for _, tl := range testLen { span, ok := getSpanFromRecorder(sr, tl.name) if !assert.True(t, ok) { continue } if tl.parent != "" { parent, ok := getSpanFromRecorder(sr, tl.parent) if assert.True(t, ok) { assert.Equal(t, span.Parent().SpanID(), parent.SpanContext().SpanID()) } } if len(tl.attributes) > 0 { attrs := span.Attributes() if tl.name == "http.getconn" { // http.local attribute uses a non-deterministic port. local := attribute.Key("http.local") var contains bool for i, a := range attrs { if a.Key == local { attrs = append(attrs[:i], attrs[i+1:]...) contains = true break } } assert.True(t, contains, "missing http.local attribute") } assert.ElementsMatch(t, tl.attributes, attrs) } } } func TestConcurrentConnectionStart(t *testing.T) { tts := []struct { name string run func(*httptrace.ClientTrace) }{ { name: "Open1Close1Open2Close2", run: func(ct *httptrace.ClientTrace) { ct.ConnectStart("tcp", "127.0.0.1:3000") ct.ConnectDone("tcp", "127.0.0.1:3000", nil) ct.ConnectStart("tcp", "[::1]:3000") ct.ConnectDone("tcp", "[::1]:3000", nil) }, }, { name: "Open2Close2Open1Close1", run: func(ct *httptrace.ClientTrace) { ct.ConnectStart("tcp", "[::1]:3000") ct.ConnectDone("tcp", "[::1]:3000", nil) ct.ConnectStart("tcp", "127.0.0.1:3000") ct.ConnectDone("tcp", "127.0.0.1:3000", nil) }, }, { name: "Open1Open2Close1Close2", run: func(ct *httptrace.ClientTrace) { ct.ConnectStart("tcp", "127.0.0.1:3000") ct.ConnectStart("tcp", "[::1]:3000") ct.ConnectDone("tcp", "127.0.0.1:3000", nil) ct.ConnectDone("tcp", "[::1]:3000", nil) }, }, { name: "Open1Open2Close2Close1", run: func(ct *httptrace.ClientTrace) { ct.ConnectStart("tcp", "127.0.0.1:3000") ct.ConnectStart("tcp", "[::1]:3000") ct.ConnectDone("tcp", "[::1]:3000", nil) ct.ConnectDone("tcp", "127.0.0.1:3000", nil) }, }, { name: "Open2Open1Close1Close2", run: func(ct *httptrace.ClientTrace) { ct.ConnectStart("tcp", "[::1]:3000") ct.ConnectStart("tcp", "127.0.0.1:3000") ct.ConnectDone("tcp", "127.0.0.1:3000", nil) ct.ConnectDone("tcp", "[::1]:3000", nil) }, }, { name: "Open2Open1Close2Close1", run: func(ct *httptrace.ClientTrace) { ct.ConnectStart("tcp", "[::1]:3000") ct.ConnectStart("tcp", "127.0.0.1:3000") ct.ConnectDone("tcp", "[::1]:3000", nil) ct.ConnectDone("tcp", "127.0.0.1:3000", nil) }, }, } expectedRemotes := []attribute.KeyValue{ attribute.String("http.remote", "127.0.0.1:3000"), attribute.String("http.conn.start.network", "tcp"), attribute.String("http.conn.done.addr", "127.0.0.1:3000"), attribute.String("http.conn.done.network", "tcp"), attribute.String("http.remote", "[::1]:3000"), attribute.String("http.conn.start.network", "tcp"), attribute.String("http.conn.done.addr", "[::1]:3000"), attribute.String("http.conn.done.network", "tcp"), } for _, tt := range tts { t.Run(tt.name, func(t *testing.T) { sr := tracetest.NewSpanRecorder() tp := trace.NewTracerProvider(trace.WithSpanProcessor(sr)) otel.SetTracerProvider(tp) tt.run(otelhttptrace.NewClientTrace(context.Background())) spans := getSpansFromRecorder(sr, "http.connect") require.Len(t, spans, 2) var gotRemotes []attribute.KeyValue for _, span := range spans { gotRemotes = append(gotRemotes, span.Attributes()...) } assert.ElementsMatch(t, expectedRemotes, gotRemotes) }) } } func TestEndBeforeStartCreatesSpan(t *testing.T) { sr := tracetest.NewSpanRecorder() tp := trace.NewTracerProvider(trace.WithSpanProcessor(sr)) otel.SetTracerProvider(tp) ct := otelhttptrace.NewClientTrace(context.Background()) ct.DNSDone(httptrace.DNSDoneInfo{}) ct.DNSStart(httptrace.DNSStartInfo{Host: "example.com"}) name := "http.dns" spans := getSpansFromRecorder(sr, name) require.Len(t, spans, 1) } func TestEndBeforeStartWithoutSubSpansDoesNotPanic(t *testing.T) { sr := tracetest.NewSpanRecorder() tp := trace.NewTracerProvider(trace.WithSpanProcessor(sr)) otel.SetTracerProvider(tp) ct := otelhttptrace.NewClientTrace(context.Background(), otelhttptrace.WithoutSubSpans()) require.NotPanics(t, func() { ct.DNSDone(httptrace.DNSDoneInfo{}) }) // no spans created because we were just using background context without span // and Start wasn't called which would have started a span require.Empty(t, sr.Ended()) } type clientTraceTestFixture struct { Address string URL string Client *http.Client SpanRecorder *tracetest.SpanRecorder } func prepareClientTraceTest(t *testing.T) clientTraceTestFixture { fixture := clientTraceTestFixture{} fixture.SpanRecorder = tracetest.NewSpanRecorder() otel.SetTracerProvider( trace.NewTracerProvider(trace.WithSpanProcessor(fixture.SpanRecorder)), ) ts := httptest.NewServer( http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { }), ) t.Cleanup(ts.Close) fixture.Client = ts.Client() fixture.URL = ts.URL fixture.Address = ts.Listener.Addr().String() return fixture } func TestWithoutSubSpans(t *testing.T) { fixture := prepareClientTraceTest(t) ctx := context.Background() ctx = httptrace.WithClientTrace(ctx, otelhttptrace.NewClientTrace(ctx, otelhttptrace.WithoutSubSpans(), ), ) req, err := http.NewRequestWithContext(ctx, http.MethodGet, fixture.URL, nil) require.NoError(t, err) resp, err := fixture.Client.Do(req) require.NoError(t, err) require.NoError(t, resp.Body.Close()) // no spans created because we were just using background context without span require.Empty(t, fixture.SpanRecorder.Ended()) // Start again with a "real" span in the context, now tracing should add // events and annotations. ctx, span := otel.Tracer("oteltest").Start(context.Background(), "root") ctx = httptrace.WithClientTrace(ctx, otelhttptrace.NewClientTrace(ctx, otelhttptrace.WithoutSubSpans(), ), ) req, err = http.NewRequestWithContext(ctx, http.MethodGet, fixture.URL, nil) req.Header.Set("User-Agent", "oteltest/1.1") req.Header.Set("Authorization", "Bearer token123") require.NoError(t, err) resp, err = fixture.Client.Do(req) require.NoError(t, err) require.NoError(t, resp.Body.Close()) span.End() // we just have the one span we created require.Len(t, fixture.SpanRecorder.Ended(), 1) recSpan := fixture.SpanRecorder.Ended()[0] gotAttributes := recSpan.Attributes() require.Len(t, gotAttributes, 4) assert.Equal(t, []attribute.KeyValue{ attribute.Key("http.request.header.host").String(fixture.Address), attribute.Key("http.request.header.user-agent").String("oteltest/1.1"), attribute.Key("http.request.header.authorization").String("****"), attribute.Key("http.request.header.accept-encoding").String("gzip"), }, gotAttributes, ) type attrMap = map[attribute.Key]attribute.Value expectedEvents := []struct { Event string VerifyAttrs func(t *testing.T, got attrMap) }{ {"http.getconn.start", func(t *testing.T, got attrMap) { assert.Equal(t, attribute.StringValue(fixture.Address), got[attribute.Key("net.host.name")], ) }}, {"http.getconn.done", func(t *testing.T, got attrMap) { // value is dynamic, just verify we have the attribute assert.Contains(t, got, attribute.Key("http.conn.idletime")) assert.Equal(t, attribute.BoolValue(true), got[attribute.Key("http.conn.reused")], ) assert.Equal(t, attribute.BoolValue(true), got[attribute.Key("http.conn.wasidle")], ) assert.Equal(t, attribute.StringValue(fixture.Address), got[attribute.Key("http.remote")], ) // value is dynamic, just verify we have the attribute assert.Contains(t, got, attribute.Key("http.local")) }}, {"http.send.start", nil}, {"http.send.done", nil}, {"http.receive.start", nil}, {"http.receive.done", nil}, } require.Len(t, recSpan.Events(), len(expectedEvents)) for i, e := range recSpan.Events() { attrs := attrMap{} for _, a := range e.Attributes { attrs[a.Key] = a.Value } expected := expectedEvents[i] assert.Equal(t, expected.Event, e.Name) if expected.VerifyAttrs == nil { assert.Nil(t, e.Attributes, "Event %q has no attributes", e.Name) } else { e := e // make loop var lexical t.Run(e.Name, func(t *testing.T) { expected.VerifyAttrs(t, attrs) }) } } } func TestWithRedactedHeaders(t *testing.T) { fixture := prepareClientTraceTest(t) ctx, span := otel.Tracer("oteltest").Start(context.Background(), "root") ctx = httptrace.WithClientTrace(ctx, otelhttptrace.NewClientTrace(ctx, otelhttptrace.WithoutSubSpans(), otelhttptrace.WithRedactedHeaders("user-agent"), ), ) req, err := http.NewRequestWithContext(ctx, http.MethodGet, fixture.URL, nil) require.NoError(t, err) resp, err := fixture.Client.Do(req) require.NoError(t, err) require.NoError(t, resp.Body.Close()) span.End() require.Len(t, fixture.SpanRecorder.Ended(), 1) recSpan := fixture.SpanRecorder.Ended()[0] gotAttributes := recSpan.Attributes() assert.Equal(t, []attribute.KeyValue{ attribute.Key("http.request.header.host").String(fixture.Address), attribute.Key("http.request.header.user-agent").String("****"), attribute.Key("http.request.header.accept-encoding").String("gzip"), }, gotAttributes, ) } func TestWithoutHeaders(t *testing.T) { fixture := prepareClientTraceTest(t) ctx, span := otel.Tracer("oteltest").Start(context.Background(), "root") ctx = httptrace.WithClientTrace(ctx, otelhttptrace.NewClientTrace(ctx, otelhttptrace.WithoutSubSpans(), otelhttptrace.WithoutHeaders(), ), ) req, err := http.NewRequestWithContext(ctx, http.MethodGet, fixture.URL, nil) require.NoError(t, err) resp, err := fixture.Client.Do(req) require.NoError(t, err) require.NoError(t, resp.Body.Close()) span.End() require.Len(t, fixture.SpanRecorder.Ended(), 1) recSpan := fixture.SpanRecorder.Ended()[0] gotAttributes := recSpan.Attributes() require.Empty(t, gotAttributes) } func TestWithInsecureHeaders(t *testing.T) { fixture := prepareClientTraceTest(t) ctx, span := otel.Tracer("oteltest").Start(context.Background(), "root") ctx = httptrace.WithClientTrace(ctx, otelhttptrace.NewClientTrace(ctx, otelhttptrace.WithoutSubSpans(), otelhttptrace.WithInsecureHeaders(), ), ) req, err := http.NewRequestWithContext(ctx, http.MethodGet, fixture.URL, nil) req.Header.Set("User-Agent", "oteltest/1.1") req.Header.Set("Authorization", "Bearer token123") require.NoError(t, err) resp, err := fixture.Client.Do(req) require.NoError(t, err) require.NoError(t, resp.Body.Close()) span.End() require.Len(t, fixture.SpanRecorder.Ended(), 1) recSpan := fixture.SpanRecorder.Ended()[0] gotAttributes := recSpan.Attributes() assert.Equal(t, []attribute.KeyValue{ attribute.Key("http.request.header.host").String(fixture.Address), attribute.Key("http.request.header.user-agent").String("oteltest/1.1"), attribute.Key("http.request.header.authorization").String("Bearer token123"), attribute.Key("http.request.header.accept-encoding").String("gzip"), }, gotAttributes, ) } func TestHTTPRequestWithTraceContext(t *testing.T) { sr := tracetest.NewSpanRecorder() tp := trace.NewTracerProvider(trace.WithSpanProcessor(sr)) // Mock http server ts := httptest.NewServer( http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { }), ) defer ts.Close() ctx, span := tp.Tracer("").Start(context.Background(), "parent_span") req, _ := http.NewRequest("GET", ts.URL, nil) req = req.WithContext(httptrace.WithClientTrace(req.Context(), otelhttptrace.NewClientTrace(ctx))) client := ts.Client() res, err := client.Do(req) require.NoError(t, err) _ = res.Body.Close() span.End() parent, ok := getSpanFromRecorder(sr, "parent_span") require.True(t, ok) getconn, ok := getSpanFromRecorder(sr, "http.getconn") require.True(t, ok) require.Equal(t, parent.SpanContext().TraceID(), getconn.SpanContext().TraceID()) require.Equal(t, parent.SpanContext().SpanID(), getconn.Parent().SpanID()) } func TestHTTPRequestWithExpect100Continue(t *testing.T) { fixture := prepareClientTraceTest(t) ctx, span := otel.Tracer("oteltest").Start(context.Background(), "root") ctx = httptrace.WithClientTrace(ctx, otelhttptrace.NewClientTrace(ctx)) req, err := http.NewRequestWithContext(ctx, http.MethodPost, fixture.URL, bytes.NewReader([]byte("test"))) require.NoError(t, err) // Set Expect: 100-continue req.Header.Set("Expect", "100-continue") resp, err := fixture.Client.Do(req) require.NoError(t, err) require.NoError(t, resp.Body.Close()) span.End() // Wait for http.send span as per https://pkg.go.dev/net/http/httptrace#ClientTrace: // Functions may be called concurrently from different goroutines and some may be called // after the request has completed var httpSendSpan trace.ReadOnlySpan require.Eventually(t, func() bool { var ok bool httpSendSpan, ok = getSpanFromRecorder(fixture.SpanRecorder, "http.send") return ok }, 5*time.Second, 10*time.Millisecond) // Found http.send span must contain "GOT 100 - Wait" event found := false for _, v := range httpSendSpan.Events() { if v.Name == "GOT 100 - Wait" { found = true break } } require.True(t, found) } doc.go000066400000000000000000000006761470323427300361460ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/httptrace/otelhttptrace/test// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 /* Package test validates the otelhttptrace instrumentation with the default SDK. This package is in a separate module from the instrumentation it tests to isolate the dependency of the default SDK and not impose this as a transitive dependency for users. */ package test // import "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace/test" go.mod000066400000000000000000000016171470323427300361540ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/httptrace/otelhttptrace/testmodule go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace/test go 1.22 require ( github.com/stretchr/testify v1.9.0 go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.56.0 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/sdk v1.31.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect go.opentelemetry.io/otel/trace v1.31.0 // indirect golang.org/x/sys v0.26.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) replace go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace => ../ replace go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp => ../../../otelhttp go.sum000066400000000000000000000051511470323427300361760ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/httptrace/otelhttptrace/testgithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= version.go000066400000000000000000000010551470323427300370560ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/httptrace/otelhttptrace/test// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package test // import "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace/test" // Version is the current release version of the httptrace instrumentation test module. func Version() string { return "0.56.0" // This string is updated by the pre_release.sh script during release } // SemVersion is the semantic version to be supplied to tracer/meter creation. // // Deprecated: Use [Version] instead. func SemVersion() string { return Version() } version.go000066400000000000000000000010451470323427300360760ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/httptrace/otelhttptrace// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelhttptrace // import "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace" // Version is the current release version of the httptrace instrumentation. func Version() string { return "0.56.0" // This string is updated by the pre_release.sh script during release } // SemVersion is the semantic version to be supplied to tracer/meter creation. // // Deprecated: Use [Version] instead. func SemVersion() string { return Version() } open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/000077500000000000000000000000001470323427300311245ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/client.go000066400000000000000000000035021470323427300327310ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" import ( "context" "io" "net/http" "net/url" "strings" ) // DefaultClient is the default Client and is used by Get, Head, Post and PostForm. // Please be careful of initialization order - for example, if you change // the global propagator, the DefaultClient might still be using the old one. var DefaultClient = &http.Client{Transport: NewTransport(http.DefaultTransport)} // Get is a convenient replacement for http.Get that adds a span around the request. func Get(ctx context.Context, targetURL string) (resp *http.Response, err error) { req, err := http.NewRequestWithContext(ctx, "GET", targetURL, nil) if err != nil { return nil, err } return DefaultClient.Do(req) } // Head is a convenient replacement for http.Head that adds a span around the request. func Head(ctx context.Context, targetURL string) (resp *http.Response, err error) { req, err := http.NewRequestWithContext(ctx, "HEAD", targetURL, nil) if err != nil { return nil, err } return DefaultClient.Do(req) } // Post is a convenient replacement for http.Post that adds a span around the request. func Post(ctx context.Context, targetURL, contentType string, body io.Reader) (resp *http.Response, err error) { req, err := http.NewRequestWithContext(ctx, "POST", targetURL, body) if err != nil { return nil, err } req.Header.Set("Content-Type", contentType) return DefaultClient.Do(req) } // PostForm is a convenient replacement for http.PostForm that adds a span around the request. func PostForm(ctx context.Context, targetURL string, data url.Values) (resp *http.Response, err error) { return Post(ctx, targetURL, "application/x-www-form-urlencoded", strings.NewReader(data.Encode())) } open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/common.go000066400000000000000000000022701470323427300327440ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" import ( "net/http" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" ) // Attribute keys that can be added to a span. const ( ReadBytesKey = attribute.Key("http.read_bytes") // if anything was read from the request body, the total number of bytes read ReadErrorKey = attribute.Key("http.read_error") // If an error occurred while reading a request, the string of the error (io.EOF is not recorded) WroteBytesKey = attribute.Key("http.wrote_bytes") // if anything was written to the response writer, the total number of bytes written WriteErrorKey = attribute.Key("http.write_error") // if an error occurred while writing a reply, the string of the error (io.EOF is not recorded) ) // Filter is a predicate used to determine whether a given http.request should // be traced. A Filter must return true if the request should be traced. type Filter func(*http.Request) bool func newTracer(tp trace.TracerProvider) trace.Tracer { return tp.Tracer(ScopeName, trace.WithInstrumentationVersion(Version())) } open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/config.go000066400000000000000000000144141470323427300327240ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" import ( "context" "net/http" "net/http/httptrace" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" ) // ScopeName is the instrumentation scope name. const ScopeName = "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" // config represents the configuration options available for the http.Handler // and http.Transport types. type config struct { ServerName string Tracer trace.Tracer Meter metric.Meter Propagators propagation.TextMapPropagator SpanStartOptions []trace.SpanStartOption PublicEndpoint bool PublicEndpointFn func(*http.Request) bool ReadEvent bool WriteEvent bool Filters []Filter SpanNameFormatter func(string, *http.Request) string ClientTrace func(context.Context) *httptrace.ClientTrace TracerProvider trace.TracerProvider MeterProvider metric.MeterProvider MetricAttributesFn func(*http.Request) []attribute.KeyValue } // Option interface used for setting optional config properties. type Option interface { apply(*config) } type optionFunc func(*config) func (o optionFunc) apply(c *config) { o(c) } // newConfig creates a new config struct and applies opts to it. func newConfig(opts ...Option) *config { c := &config{ Propagators: otel.GetTextMapPropagator(), MeterProvider: otel.GetMeterProvider(), } for _, opt := range opts { opt.apply(c) } // Tracer is only initialized if manually specified. Otherwise, can be passed with the tracing context. if c.TracerProvider != nil { c.Tracer = newTracer(c.TracerProvider) } c.Meter = c.MeterProvider.Meter( ScopeName, metric.WithInstrumentationVersion(Version()), ) return c } // WithTracerProvider specifies a tracer provider to use for creating a tracer. // If none is specified, the global provider is used. func WithTracerProvider(provider trace.TracerProvider) Option { return optionFunc(func(cfg *config) { if provider != nil { cfg.TracerProvider = provider } }) } // WithMeterProvider specifies a meter provider to use for creating a meter. // If none is specified, the global provider is used. func WithMeterProvider(provider metric.MeterProvider) Option { return optionFunc(func(cfg *config) { if provider != nil { cfg.MeterProvider = provider } }) } // WithPublicEndpoint configures the Handler to link the span with an incoming // span context. If this option is not provided, then the association is a child // association instead of a link. func WithPublicEndpoint() Option { return optionFunc(func(c *config) { c.PublicEndpoint = true }) } // WithPublicEndpointFn runs with every request, and allows conditionally // configuring the Handler to link the span with an incoming span context. If // this option is not provided or returns false, then the association is a // child association instead of a link. // Note: WithPublicEndpoint takes precedence over WithPublicEndpointFn. func WithPublicEndpointFn(fn func(*http.Request) bool) Option { return optionFunc(func(c *config) { c.PublicEndpointFn = fn }) } // WithPropagators configures specific propagators. If this // option isn't specified, then the global TextMapPropagator is used. func WithPropagators(ps propagation.TextMapPropagator) Option { return optionFunc(func(c *config) { if ps != nil { c.Propagators = ps } }) } // WithSpanOptions configures an additional set of // trace.SpanOptions, which are applied to each new span. func WithSpanOptions(opts ...trace.SpanStartOption) Option { return optionFunc(func(c *config) { c.SpanStartOptions = append(c.SpanStartOptions, opts...) }) } // WithFilter adds a filter to the list of filters used by the handler. // If any filter indicates to exclude a request then the request will not be // traced. All filters must allow a request to be traced for a Span to be created. // If no filters are provided then all requests are traced. // Filters will be invoked for each processed request, it is advised to make them // simple and fast. func WithFilter(f Filter) Option { return optionFunc(func(c *config) { c.Filters = append(c.Filters, f) }) } type event int // Different types of events that can be recorded, see WithMessageEvents. const ( ReadEvents event = iota WriteEvents ) // WithMessageEvents configures the Handler to record the specified events // (span.AddEvent) on spans. By default only summary attributes are added at the // end of the request. // // Valid events are: // - ReadEvents: Record the number of bytes read after every http.Request.Body.Read // using the ReadBytesKey // - WriteEvents: Record the number of bytes written after every http.ResponeWriter.Write // using the WriteBytesKey func WithMessageEvents(events ...event) Option { return optionFunc(func(c *config) { for _, e := range events { switch e { case ReadEvents: c.ReadEvent = true case WriteEvents: c.WriteEvent = true } } }) } // WithSpanNameFormatter takes a function that will be called on every // request and the returned string will become the Span Name. func WithSpanNameFormatter(f func(operation string, r *http.Request) string) Option { return optionFunc(func(c *config) { c.SpanNameFormatter = f }) } // WithClientTrace takes a function that returns client trace instance that will be // applied to the requests sent through the otelhttp Transport. func WithClientTrace(f func(context.Context) *httptrace.ClientTrace) Option { return optionFunc(func(c *config) { c.ClientTrace = f }) } // WithServerName returns an Option that sets the name of the (virtual) server // handling requests. func WithServerName(server string) Option { return optionFunc(func(c *config) { c.ServerName = server }) } // WithMetricAttributesFn returns an Option to set a function that maps an HTTP request to a slice of attribute.KeyValue. // These attributes will be included in metrics for every request. func WithMetricAttributesFn(metricAttributesFn func(r *http.Request) []attribute.KeyValue) Option { return optionFunc(func(c *config) { c.MetricAttributesFn = metricAttributesFn }) } open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/doc.go000066400000000000000000000005351470323427300322230ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package otelhttp provides an http.Handler and functions that are intended // to be used to add tracing by wrapping existing handlers (with Handler) and // routes WithRouteTag. package otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/example/000077500000000000000000000000001470323427300325575ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/example/Dockerfile000066400000000000000000000005461470323427300345560ustar00rootroot00000000000000# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 FROM golang:1.23-alpine AS base COPY . /src/ WORKDIR /src/instrumentation/net/http/otelhttp/example FROM base AS example-http-server RUN go install ./server/server.go CMD ["/go/bin/server"] FROM base AS example-http-client RUN go install ./client/client.go CMD ["/go/bin/client"] open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/example/README.md000066400000000000000000000013401470323427300340340ustar00rootroot00000000000000# HTTP Client-Server Example An HTTP client connects to an HTTP server. They both generate span information to `stdout`. These instructions expect you have [docker-compose](https://docs.docker.com/compose/) installed. Bring up the `http-server` and `http-client` services to run the example: ```sh docker-compose up --detach http-server http-client ``` The `http-client` service sends just one HTTP request to `http-server` and then exits. View the span and metric generated to `stdout` in the logs: ```sh docker-compose logs http-client ``` View the span generated by `http-server` in the logs: ```sh docker-compose logs http-server ``` Shut down the services when you are finished with the example: ```sh docker-compose down ``` open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/example/client/000077500000000000000000000000001470323427300340355ustar00rootroot00000000000000client.go000066400000000000000000000045501470323427300355670ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/example/client// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package main import ( "context" "flag" "fmt" "io" "log" "net/http" "time" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/baggage" stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/propagation" sdktrace "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.20.0" "go.opentelemetry.io/otel/trace" ) func initTracer() (*sdktrace.TracerProvider, error) { // Create stdout exporter to be able to retrieve // the collected spans. exporter, err := stdout.New(stdout.WithPrettyPrint()) if err != nil { return nil, err } // For the demonstration, use sdktrace.AlwaysSample sampler to sample all traces. // In a production application, use sdktrace.ProbabilitySampler with a desired probability. tp := sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithBatcher(exporter), ) otel.SetTracerProvider(tp) otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) return tp, err } func main() { tp, err := initTracer() if err != nil { log.Fatal(err) } defer func() { if err := tp.Shutdown(context.Background()); err != nil { log.Printf("Error shutting down tracer provider: %v", err) } }() url := flag.String("server", "http://localhost:7777/hello", "server url") flag.Parse() client := http.Client{Transport: otelhttp.NewTransport(http.DefaultTransport)} bag, _ := baggage.Parse("username=donuts") ctx := baggage.ContextWithBaggage(context.Background(), bag) var body []byte tr := otel.Tracer("example/client") err = func(ctx context.Context) error { ctx, span := tr.Start(ctx, "say hello", trace.WithAttributes(semconv.PeerService("ExampleService"))) defer span.End() req, _ := http.NewRequestWithContext(ctx, "GET", *url, nil) fmt.Printf("Sending request...\n") res, err := client.Do(req) if err != nil { panic(err) } body, err = io.ReadAll(res.Body) _ = res.Body.Close() return err }(ctx) if err != nil { log.Fatal(err) } fmt.Printf("Response Received: %s\n\n\n", body) fmt.Printf("Waiting for few seconds to export spans ...\n\n") time.Sleep(10 * time.Second) fmt.Printf("Inspect traces on stdout\n") } docker-compose.yml000066400000000000000000000010361470323427300361350ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/example# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 version: "3.7" services: http-server: build: dockerfile: $PWD/Dockerfile context: ../../../../.. target: example-http-server networks: - example http-client: build: dockerfile: $PWD/Dockerfile context: ../../../../.. target: example-http-client command: ["/go/bin/client", "-server", "http://http-server:7777/hello"] networks: - example depends_on: - http-server networks: example: open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/example/go.mod000066400000000000000000000014601470323427300336660ustar00rootroot00000000000000module go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/example go 1.22 replace go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp => ../ require ( go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.31.0 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0 go.opentelemetry.io/otel/sdk v1.31.0 go.opentelemetry.io/otel/sdk/metric v1.31.0 go.opentelemetry.io/otel/trace v1.31.0 ) require ( github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect golang.org/x/sys v0.26.0 // indirect ) open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/example/go.sum000066400000000000000000000060371470323427300337200ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.31.0 h1:HZgBIps9wH0RDrwjrmNa3DVbNRW60HEhdzqZFyAp3fI= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.31.0/go.mod h1:RDRhvt6TDG0eIXmonAx5bd9IcwpqCkziwkOClzWKwAQ= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0 h1:UGZ1QwZWY67Z6BmckTU+9Rxn04m2bD3gD6Mk0OIOCPk= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0/go.mod h1:fcwWuDuaObkkChiDlhEpSq9+X1C0omv+s5mBtToAQ64= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc= go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/example/server/000077500000000000000000000000001470323427300340655ustar00rootroot00000000000000modd.conf000066400000000000000000000004161470323427300356010ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/example/server# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 # A basic modd.conf file for Go development. # Run go test on ALL modules on startup, and subsequently only on modules # containing changes. server.go { daemon +sigterm: go run server.go }server.go000066400000000000000000000054241470323427300356500ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/example/server// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package main import ( "context" "io" "log" "net/http" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/baggage" "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/propagation" sdkmetric "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.20.0" "go.opentelemetry.io/otel/trace" ) func initTracer() (*sdktrace.TracerProvider, error) { // Create stdout exporter to be able to retrieve // the collected spans. exporter, err := stdouttrace.New(stdouttrace.WithPrettyPrint()) if err != nil { return nil, err } // For the demonstration, use sdktrace.AlwaysSample sampler to sample all traces. // In a production application, use sdktrace.ProbabilitySampler with a desired probability. tp := sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithBatcher(exporter), sdktrace.WithResource(resource.NewWithAttributes(semconv.SchemaURL, semconv.ServiceName("ExampleService"))), ) otel.SetTracerProvider(tp) otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) return tp, err } func initMeter() (*sdkmetric.MeterProvider, error) { exp, err := stdoutmetric.New() if err != nil { return nil, err } mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(sdkmetric.NewPeriodicReader(exp))) otel.SetMeterProvider(mp) return mp, nil } func main() { tp, err := initTracer() if err != nil { log.Fatal(err) } defer func() { if err := tp.Shutdown(context.Background()); err != nil { log.Printf("Error shutting down tracer provider: %v", err) } }() mp, err := initMeter() if err != nil { log.Fatal(err) } defer func() { if err := mp.Shutdown(context.Background()); err != nil { log.Printf("Error shutting down meter provider: %v", err) } }() uk := attribute.Key("username") helloHandler := func(w http.ResponseWriter, req *http.Request) { ctx := req.Context() span := trace.SpanFromContext(ctx) bag := baggage.FromContext(ctx) span.AddEvent("handling this...", trace.WithAttributes(uk.String(bag.Member("username").Value()))) _, _ = io.WriteString(w, "Hello, world!\n") } otelHandler := otelhttp.NewHandler(http.HandlerFunc(helloHandler), "Hello") http.Handle("/hello", otelHandler) err = http.ListenAndServe(":7777", nil) //nolint:gosec // Ignoring G114: Use of net/http serve function that has no support for setting timeouts. if err != nil { log.Fatal(err) } } open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/filters/000077500000000000000000000000001470323427300325745ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/filters/filters.go000066400000000000000000000056151470323427300346020ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package filters provides a set of filters useful with the // otelhttp.WithFilter() option to control which inbound requests are traced. package filters // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/filters" import ( "net/http" "strings" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) // Any takes a list of Filters and returns a Filter that // returns true if any Filter in the list returns true. func Any(fs ...otelhttp.Filter) otelhttp.Filter { return func(r *http.Request) bool { for _, f := range fs { if f(r) { return true } } return false } } // All takes a list of Filters and returns a Filter that // returns true only if all Filters in the list return true. func All(fs ...otelhttp.Filter) otelhttp.Filter { return func(r *http.Request) bool { for _, f := range fs { if !f(r) { return false } } return true } } // None takes a list of Filters and returns a Filter that returns // true only if none of the Filters in the list return true. func None(fs ...otelhttp.Filter) otelhttp.Filter { return func(r *http.Request) bool { for _, f := range fs { if f(r) { return false } } return true } } // Not provides a convenience mechanism for inverting a Filter. func Not(f otelhttp.Filter) otelhttp.Filter { return func(r *http.Request) bool { return !f(r) } } // Hostname returns a Filter that returns true if the request's // hostname matches the provided string. func Hostname(h string) otelhttp.Filter { return func(r *http.Request) bool { return r.URL.Hostname() == h } } // Path returns a Filter that returns true if the request's // path matches the provided string. func Path(p string) otelhttp.Filter { return func(r *http.Request) bool { return r.URL.Path == p } } // PathPrefix returns a Filter that returns true if the request's // path starts with the provided string. func PathPrefix(p string) otelhttp.Filter { return func(r *http.Request) bool { return strings.HasPrefix(r.URL.Path, p) } } // Query returns a Filter that returns true if the request // includes a query parameter k with a value equal to v. func Query(k, v string) otelhttp.Filter { return func(r *http.Request) bool { for _, qv := range r.URL.Query()[k] { if v == qv { return true } } return false } } // QueryContains returns a Filter that returns true if the request // includes a query parameter k with a value that contains v. func QueryContains(k, v string) otelhttp.Filter { return func(r *http.Request) bool { for _, qv := range r.URL.Query()[k] { if strings.Contains(qv, v) { return true } } return false } } // Method returns a Filter that returns true if the request // method is equal to the provided value. func Method(m string) otelhttp.Filter { return func(r *http.Request) bool { return m == r.Method } } filters_test.go000066400000000000000000000150241470323427300355550ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/filters// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package filters import ( "net/http" "net/url" "testing" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) type scenario struct { name string filter otelhttp.Filter req *http.Request exp bool } func TestAny(t *testing.T) { for _, s := range []scenario{ { name: "no matching filters", filter: Any(Path("/foo"), Hostname("bar.baz")), req: &http.Request{URL: &url.URL{Path: "/boo", Host: "baz.bar:8080"}}, exp: false, }, { name: "one matching filter", filter: Any(Path("/foo"), Hostname("bar.baz")), req: &http.Request{URL: &url.URL{Path: "/foo", Host: "baz.bar:8080"}}, exp: true, }, { name: "all matching filters", filter: Any(Path("/foo"), Hostname("bar.baz")), req: &http.Request{URL: &url.URL{Path: "/foo", Host: "bar.baz:8080"}}, exp: true, }, } { res := s.filter(s.req) if s.exp != res { t.Errorf("Failed testing %q. Expected %t, got %t", s.name, s.exp, res) } } } func TestAll(t *testing.T) { for _, s := range []scenario{ { name: "no matching filters", filter: All(Path("/foo"), Hostname("bar.baz")), req: &http.Request{URL: &url.URL{Path: "/boo", Host: "baz.bar:8080"}}, exp: false, }, { name: "one matching filter", filter: All(Path("/foo"), Hostname("bar.baz")), req: &http.Request{URL: &url.URL{Path: "/foo", Host: "baz.bar:8080"}}, exp: false, }, { name: "all matching filters", filter: All(Path("/foo"), Hostname("bar.baz")), req: &http.Request{URL: &url.URL{Path: "/foo", Host: "bar.baz:8080"}}, exp: true, }, } { res := s.filter(s.req) if s.exp != res { t.Errorf("Failed testing %q. Expected %t, got %t", s.name, s.exp, res) } } } func TestNone(t *testing.T) { for _, s := range []scenario{ { name: "no matching filters", filter: None(Path("/foo"), Hostname("bar.baz")), req: &http.Request{URL: &url.URL{Path: "/boo", Host: "baz.bar:8080"}}, exp: true, }, { name: "one matching filter", filter: None(Path("/foo"), Hostname("bar.baz")), req: &http.Request{URL: &url.URL{Path: "/foo", Host: "baz.bar:8080"}}, exp: false, }, { name: "all matching filters", filter: None(Path("/foo"), Hostname("bar.baz")), req: &http.Request{URL: &url.URL{Path: "/foo", Host: "bar.baz:8080"}}, exp: false, }, } { res := s.filter(s.req) if s.exp != res { t.Errorf("Failed testing %q. Expected %t, got %t", s.name, s.exp, res) } } } func TestNot(t *testing.T) { req := &http.Request{URL: &url.URL{Path: "/foo", Host: "bar.baz:8080"}} filter := Path("/foo") if filter(req) == Not(filter)(req) { t.Error("Not filter should invert the result of the supplied filter") } } func TestPathPrefix(t *testing.T) { for _, s := range []scenario{ { name: "non-matching prefix", filter: PathPrefix("/foo"), req: &http.Request{URL: &url.URL{Path: "/boo/far", Host: "baz.bar:8080"}}, exp: false, }, { name: "matching prefix", filter: PathPrefix("/foo"), req: &http.Request{URL: &url.URL{Path: "/foo/bar", Host: "bar.baz:8080"}}, exp: true, }, } { res := s.filter(s.req) if s.exp != res { t.Errorf("Failed testing %q. Expected %t, got %t", s.name, s.exp, res) } } } func TestMethod(t *testing.T) { for _, s := range []scenario{ { name: "non-matching method", filter: Method(http.MethodGet), req: &http.Request{Method: http.MethodHead, URL: &url.URL{Path: "/boo/far", Host: "baz.bar:8080"}}, exp: false, }, { name: "matching method", filter: Method(http.MethodGet), req: &http.Request{Method: http.MethodGet, URL: &url.URL{Path: "/boo/far", Host: "baz.bar:8080"}}, exp: true, }, } { res := s.filter(s.req) if s.exp != res { t.Errorf("Failed testing %q. Expected %t, got %t", s.name, s.exp, res) } } } func TestQuery(t *testing.T) { matching, _ := url.Parse("http://bar.baz:8080/foo/bar?key=value") nonMatching, _ := url.Parse("http://bar.baz:8080/foo/bar?key=other") for _, s := range []scenario{ { name: "non-matching query parameter", filter: Query("key", "value"), req: &http.Request{Method: http.MethodHead, URL: nonMatching}, exp: false, }, { name: "matching query parameter", filter: Query("key", "value"), req: &http.Request{Method: http.MethodGet, URL: matching}, exp: true, }, } { res := s.filter(s.req) if s.exp != res { t.Errorf("Failed testing %q. Expected %t, got %t", s.name, s.exp, res) } } } func TestQueryContains(t *testing.T) { matching, _ := url.Parse("http://bar.baz:8080/foo/bar?key=value") nonMatching, _ := url.Parse("http://bar.baz:8080/foo/bar?key=other") for _, s := range []scenario{ { name: "non-matching query parameter", filter: QueryContains("key", "alu"), req: &http.Request{Method: http.MethodHead, URL: nonMatching}, exp: false, }, { name: "matching query parameter", filter: QueryContains("key", "alu"), req: &http.Request{Method: http.MethodGet, URL: matching}, exp: true, }, } { res := s.filter(s.req) if s.exp != res { t.Errorf("Failed testing %q. Expected %t, got %t", s.name, s.exp, res) } } } func TestHeader(t *testing.T) { matching := http.Header{} matching.Add("key", "value") nonMatching := http.Header{} nonMatching.Add("key", "other") for _, s := range []scenario{ { name: "non-matching query parameter", filter: Header("key", "value"), req: &http.Request{Method: http.MethodHead, Header: nonMatching}, exp: false, }, { name: "matching query parameter", filter: Header("key", "value"), req: &http.Request{Method: http.MethodGet, Header: matching}, exp: true, }, } { res := s.filter(s.req) if s.exp != res { t.Errorf("Failed testing %q. Expected %t, got %t", s.name, s.exp, res) } } } func TestHeaderContains(t *testing.T) { matching := http.Header{} matching.Add("key", "value") nonMatching := http.Header{} nonMatching.Add("key", "other") for _, s := range []scenario{ { name: "non-matching query parameter", filter: HeaderContains("key", "alu"), req: &http.Request{Method: http.MethodHead, Header: nonMatching}, exp: false, }, { name: "matching query parameter", filter: HeaderContains("key", "alu"), req: &http.Request{Method: http.MethodGet, Header: matching}, exp: true, }, } { res := s.filter(s.req) if s.exp != res { t.Errorf("Failed testing %q. Expected %t, got %t", s.name, s.exp, res) } } } open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/filters/header.go000066400000000000000000000017121470323427300343540ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package filters // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/filters" import ( "net/http" "net/textproto" "strings" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) // Header returns a Filter that returns true if the request // includes a header k with a value equal to v. func Header(k, v string) otelhttp.Filter { return func(r *http.Request) bool { for _, hv := range r.Header[textproto.CanonicalMIMEHeaderKey(k)] { if v == hv { return true } } return false } } // HeaderContains returns a Filter that returns true if the request // includes a header k with a value that contains v. func HeaderContains(k, v string) otelhttp.Filter { return func(r *http.Request) bool { for _, hv := range r.Header[textproto.CanonicalMIMEHeaderKey(k)] { if strings.Contains(hv, v) { return true } } return false } } open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/go.mod000066400000000000000000000010021470323427300322230ustar00rootroot00000000000000module go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp go 1.22 require ( github.com/felixge/httpsnoop v1.0.4 github.com/stretchr/testify v1.9.0 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/metric v1.31.0 go.opentelemetry.io/otel/trace v1.31.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/go.sum000066400000000000000000000041741470323427300322650ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/handler.go000066400000000000000000000146701470323427300331000ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" import ( "net/http" "time" "github.com/felixge/httpsnoop" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/request" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" ) // middleware is an http middleware which wraps the next handler in a span. type middleware struct { operation string server string tracer trace.Tracer propagators propagation.TextMapPropagator spanStartOptions []trace.SpanStartOption readEvent bool writeEvent bool filters []Filter spanNameFormatter func(string, *http.Request) string publicEndpoint bool publicEndpointFn func(*http.Request) bool semconv semconv.HTTPServer } func defaultHandlerFormatter(operation string, _ *http.Request) string { return operation } // NewHandler wraps the passed handler in a span named after the operation and // enriches it with metrics. func NewHandler(handler http.Handler, operation string, opts ...Option) http.Handler { return NewMiddleware(operation, opts...)(handler) } // NewMiddleware returns a tracing and metrics instrumentation middleware. // The handler returned by the middleware wraps a handler // in a span named after the operation and enriches it with metrics. func NewMiddleware(operation string, opts ...Option) func(http.Handler) http.Handler { h := middleware{ operation: operation, } defaultOpts := []Option{ WithSpanOptions(trace.WithSpanKind(trace.SpanKindServer)), WithSpanNameFormatter(defaultHandlerFormatter), } c := newConfig(append(defaultOpts, opts...)...) h.configure(c) return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { h.serveHTTP(w, r, next) }) } } func (h *middleware) configure(c *config) { h.tracer = c.Tracer h.propagators = c.Propagators h.spanStartOptions = c.SpanStartOptions h.readEvent = c.ReadEvent h.writeEvent = c.WriteEvent h.filters = c.Filters h.spanNameFormatter = c.SpanNameFormatter h.publicEndpoint = c.PublicEndpoint h.publicEndpointFn = c.PublicEndpointFn h.server = c.ServerName h.semconv = semconv.NewHTTPServer(c.Meter) } // serveHTTP sets up tracing and calls the given next http.Handler with the span // context injected into the request context. func (h *middleware) serveHTTP(w http.ResponseWriter, r *http.Request, next http.Handler) { requestStartTime := time.Now() for _, f := range h.filters { if !f(r) { // Simply pass through to the handler if a filter rejects the request next.ServeHTTP(w, r) return } } ctx := h.propagators.Extract(r.Context(), propagation.HeaderCarrier(r.Header)) opts := []trace.SpanStartOption{ trace.WithAttributes(h.semconv.RequestTraceAttrs(h.server, r)...), } opts = append(opts, h.spanStartOptions...) if h.publicEndpoint || (h.publicEndpointFn != nil && h.publicEndpointFn(r.WithContext(ctx))) { opts = append(opts, trace.WithNewRoot()) // Linking incoming span context if any for public endpoint. if s := trace.SpanContextFromContext(ctx); s.IsValid() && s.IsRemote() { opts = append(opts, trace.WithLinks(trace.Link{SpanContext: s})) } } tracer := h.tracer if tracer == nil { if span := trace.SpanFromContext(r.Context()); span.SpanContext().IsValid() { tracer = newTracer(span.TracerProvider()) } else { tracer = newTracer(otel.GetTracerProvider()) } } ctx, span := tracer.Start(ctx, h.spanNameFormatter(h.operation, r), opts...) defer span.End() readRecordFunc := func(int64) {} if h.readEvent { readRecordFunc = func(n int64) { span.AddEvent("read", trace.WithAttributes(ReadBytesKey.Int64(n))) } } // if request body is nil or NoBody, we don't want to mutate the body as it // will affect the identity of it in an unforeseeable way because we assert // ReadCloser fulfills a certain interface and it is indeed nil or NoBody. bw := request.NewBodyWrapper(r.Body, readRecordFunc) if r.Body != nil && r.Body != http.NoBody { r.Body = bw } writeRecordFunc := func(int64) {} if h.writeEvent { writeRecordFunc = func(n int64) { span.AddEvent("write", trace.WithAttributes(WroteBytesKey.Int64(n))) } } rww := request.NewRespWriterWrapper(w, writeRecordFunc) // Wrap w to use our ResponseWriter methods while also exposing // other interfaces that w may implement (http.CloseNotifier, // http.Flusher, http.Hijacker, http.Pusher, io.ReaderFrom). w = httpsnoop.Wrap(w, httpsnoop.Hooks{ Header: func(httpsnoop.HeaderFunc) httpsnoop.HeaderFunc { return rww.Header }, Write: func(httpsnoop.WriteFunc) httpsnoop.WriteFunc { return rww.Write }, WriteHeader: func(httpsnoop.WriteHeaderFunc) httpsnoop.WriteHeaderFunc { return rww.WriteHeader }, Flush: func(httpsnoop.FlushFunc) httpsnoop.FlushFunc { return rww.Flush }, }) labeler, found := LabelerFromContext(ctx) if !found { ctx = ContextWithLabeler(ctx, labeler) } next.ServeHTTP(w, r.WithContext(ctx)) statusCode := rww.StatusCode() bytesWritten := rww.BytesWritten() span.SetStatus(h.semconv.Status(statusCode)) span.SetAttributes(h.semconv.ResponseTraceAttrs(semconv.ResponseTelemetry{ StatusCode: statusCode, ReadBytes: bw.BytesRead(), ReadError: bw.Error(), WriteBytes: bytesWritten, WriteError: rww.Error(), })...) // Use floating point division here for higher precision (instead of Millisecond method). elapsedTime := float64(time.Since(requestStartTime)) / float64(time.Millisecond) h.semconv.RecordMetrics(ctx, semconv.ServerMetricData{ ServerName: h.server, ResponseSize: bytesWritten, MetricAttributes: semconv.MetricAttributes{ Req: r, StatusCode: statusCode, AdditionalAttributes: labeler.Get(), }, MetricData: semconv.MetricData{ RequestSize: bw.BytesRead(), ElapsedTime: elapsedTime, }, }) } // WithRouteTag annotates spans and metrics with the provided route name // with HTTP route attribute. func WithRouteTag(route string, h http.Handler) http.Handler { attr := semconv.NewHTTPServer(nil).Route(route) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { span := trace.SpanFromContext(r.Context()) span.SetAttributes(attr) labeler, _ := LabelerFromContext(r.Context()) labeler.Add(attr) h.ServeHTTP(w, r) }) } handler_example_test.go000066400000000000000000000047631470323427300355750ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelhttp_test import ( "context" "fmt" "io" "log" "net/http" "strings" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) func ExampleNewHandler() { /* curl -v -d "a painting" http://localhost:7777/hello/bob/ross ... * upload completely sent off: 10 out of 10 bytes < HTTP/1.1 200 OK < Traceparent: 00-76ae040ee5753f38edf1c2bd9bd128bd-dd394138cfd7a3dc-01 < Date: Fri, 04 Oct 2019 02:33:08 GMT < Content-Length: 45 < Content-Type: text/plain; charset=utf-8 < Hello, bob/ross! You sent me this: a painting */ figureOutName := func(ctx context.Context, s string) (string, error) { pp := strings.SplitN(s, "/", 2) var err error switch pp[1] { case "": err = fmt.Errorf("expected /hello/:name in %q", s) default: trace.SpanFromContext(ctx).SetAttributes(attribute.String("name", pp[1])) } return pp[1], err } var mux http.ServeMux mux.Handle("/hello/", otelhttp.WithRouteTag("/hello/:name", http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() labeler, _ := otelhttp.LabelerFromContext(ctx) var name string // Wrap another function in its own span if err := func(ctx context.Context) error { ctx, span := trace.SpanFromContext(ctx).TracerProvider().Tracer("exampleTracer").Start(ctx, "figureOutName") defer span.End() var err error name, err = figureOutName(ctx, r.URL.Path[1:]) return err }(ctx); err != nil { log.Println("error figuring out name: ", err) http.Error(w, err.Error(), http.StatusInternalServerError) labeler.Add(attribute.Bool("error", true)) return } d, err := io.ReadAll(r.Body) if err != nil { log.Println("error reading body: ", err) w.WriteHeader(http.StatusBadRequest) labeler.Add(attribute.Bool("error", true)) return } n, err := io.WriteString(w, "Hello, "+name+"!\nYou sent me this:\n"+string(d)) if err != nil { log.Printf("error writing reply after %d bytes: %s", n, err) labeler.Add(attribute.Bool("error", true)) } }), ), ) if err := http.ListenAndServe(":7777", //nolint:gosec // Ignoring G114: Use of net/http serve function that has no support for setting timeouts. otelhttp.NewHandler(&mux, "server", otelhttp.WithMessageEvents(otelhttp.ReadEvents, otelhttp.WriteEvents), ), ); err != nil { log.Fatal(err) } } open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/handler_test.go000066400000000000000000000046251470323427300341360ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelhttp_test import ( "io" "net/http" "net/http/httptest" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) func TestHandler(t *testing.T) { testCases := []struct { name string handler func(*testing.T) http.Handler requestBody io.Reader expectedStatusCode int }{ { name: "implements flusher", handler: func(t *testing.T) http.Handler { return otelhttp.NewHandler( http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Implements(t, (*http.Flusher)(nil), w) w.(http.Flusher).Flush() _, _ = io.WriteString(w, "Hello, world!\n") }), "test_handler", ) }, expectedStatusCode: http.StatusOK, }, { name: "succeeds", handler: func(t *testing.T) http.Handler { return otelhttp.NewHandler( http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.NotNil(t, r.Body) b, err := io.ReadAll(r.Body) assert.NoError(t, err) assert.Equal(t, "hello world", string(b)) }), "test_handler", ) }, requestBody: strings.NewReader("hello world"), expectedStatusCode: http.StatusOK, }, { name: "succeeds with a nil body", handler: func(t *testing.T) http.Handler { return otelhttp.NewHandler( http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Nil(t, r.Body) }), "test_handler", ) }, expectedStatusCode: http.StatusOK, }, { name: "succeeds with an http.NoBody", handler: func(t *testing.T) http.Handler { return otelhttp.NewHandler( http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, http.NoBody, r.Body) }), "test_handler", ) }, requestBody: http.NoBody, expectedStatusCode: http.StatusOK, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { r, err := http.NewRequest(http.MethodGet, "http://localhost/", tc.requestBody) require.NoError(t, err) rr := httptest.NewRecorder() tc.handler(t).ServeHTTP(rr, r) assert.Equal(t, tc.expectedStatusCode, rr.Result().StatusCode) //nolint:bodyclose // False positive for httptest.ResponseRecorder: https://github.com/timakin/bodyclose/issues/59. }) } } open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/internal/000077500000000000000000000000001470323427300327405ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/internal/request/000077500000000000000000000000001470323427300344305ustar00rootroot00000000000000body_wrapper.go000066400000000000000000000031071470323427300373760ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/internal/request// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package request // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/request" import ( "io" "sync" ) var _ io.ReadCloser = &BodyWrapper{} // BodyWrapper wraps a http.Request.Body (an io.ReadCloser) to track the number // of bytes read and the last error. type BodyWrapper struct { io.ReadCloser OnRead func(n int64) // must not be nil mu sync.Mutex read int64 err error } // NewBodyWrapper creates a new BodyWrapper. // // The onRead attribute is a callback that will be called every time the data // is read, with the number of bytes being read. func NewBodyWrapper(body io.ReadCloser, onRead func(int64)) *BodyWrapper { return &BodyWrapper{ ReadCloser: body, OnRead: onRead, } } // Read reads the data from the io.ReadCloser, and stores the number of bytes // read and the error. func (w *BodyWrapper) Read(b []byte) (int, error) { n, err := w.ReadCloser.Read(b) n1 := int64(n) w.updateReadData(n1, err) w.OnRead(n1) return n, err } func (w *BodyWrapper) updateReadData(n int64, err error) { w.mu.Lock() defer w.mu.Unlock() w.read += n if err != nil { w.err = err } } // Closes closes the io.ReadCloser. func (w *BodyWrapper) Close() error { return w.ReadCloser.Close() } // BytesRead returns the number of bytes read up to this point. func (w *BodyWrapper) BytesRead() int64 { w.mu.Lock() defer w.mu.Unlock() return w.read } // Error returns the last error. func (w *BodyWrapper) Error() error { w.mu.Lock() defer w.mu.Unlock() return w.err } body_wrapper_test.go000066400000000000000000000031551470323427300404400ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/internal/request// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package request import ( "errors" "io" "strings" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) var errFirstCall = errors.New("first call") func TestBodyWrapper(t *testing.T) { bw := NewBodyWrapper(io.NopCloser(strings.NewReader("hello world")), func(int64) {}) data, err := io.ReadAll(bw) require.NoError(t, err) assert.Equal(t, "hello world", string(data)) assert.Equal(t, int64(11), bw.BytesRead()) assert.Equal(t, io.EOF, bw.Error()) } type multipleErrorsReader struct { calls int } type errorWrapper struct{} func (errorWrapper) Error() string { return "subsequent calls" } func (mer *multipleErrorsReader) Read([]byte) (int, error) { mer.calls = mer.calls + 1 if mer.calls == 1 { return 0, errFirstCall } return 0, errorWrapper{} } func TestBodyWrapperWithErrors(t *testing.T) { bw := NewBodyWrapper(io.NopCloser(&multipleErrorsReader{}), func(int64) {}) data, err := io.ReadAll(bw) require.Equal(t, errFirstCall, err) assert.Equal(t, "", string(data)) require.Equal(t, errFirstCall, bw.Error()) data, err = io.ReadAll(bw) require.Equal(t, errorWrapper{}, err) assert.Equal(t, "", string(data)) require.Equal(t, errorWrapper{}, bw.Error()) } func TestConcurrentBodyWrapper(t *testing.T) { bw := NewBodyWrapper(io.NopCloser(strings.NewReader("hello world")), func(int64) {}) go func() { _, _ = io.ReadAll(bw) }() assert.NotNil(t, bw.BytesRead()) assert.Eventually(t, func() bool { return errors.Is(bw.Error(), io.EOF) }, time.Second, 10*time.Millisecond) } resp_writer_wrapper.go000066400000000000000000000063161470323427300410130ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/internal/request// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package request // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/request" import ( "net/http" "sync" ) var _ http.ResponseWriter = &RespWriterWrapper{} // RespWriterWrapper wraps a http.ResponseWriter in order to track the number of // bytes written, the last error, and to catch the first written statusCode. // TODO: The wrapped http.ResponseWriter doesn't implement any of the optional // types (http.Hijacker, http.Pusher, http.CloseNotifier, etc) // that may be useful when using it in real life situations. type RespWriterWrapper struct { http.ResponseWriter OnWrite func(n int64) // must not be nil mu sync.RWMutex written int64 statusCode int err error wroteHeader bool } // NewRespWriterWrapper creates a new RespWriterWrapper. // // The onWrite attribute is a callback that will be called every time the data // is written, with the number of bytes that were written. func NewRespWriterWrapper(w http.ResponseWriter, onWrite func(int64)) *RespWriterWrapper { return &RespWriterWrapper{ ResponseWriter: w, OnWrite: onWrite, statusCode: http.StatusOK, // default status code in case the Handler doesn't write anything } } // Write writes the bytes array into the [ResponseWriter], and tracks the // number of bytes written and last error. func (w *RespWriterWrapper) Write(p []byte) (int, error) { w.mu.Lock() defer w.mu.Unlock() if !w.wroteHeader { w.writeHeader(http.StatusOK) } n, err := w.ResponseWriter.Write(p) n1 := int64(n) w.OnWrite(n1) w.written += n1 w.err = err return n, err } // WriteHeader persists initial statusCode for span attribution. // All calls to WriteHeader will be propagated to the underlying ResponseWriter // and will persist the statusCode from the first call. // Blocking consecutive calls to WriteHeader alters expected behavior and will // remove warning logs from net/http where developers will notice incorrect handler implementations. func (w *RespWriterWrapper) WriteHeader(statusCode int) { w.mu.Lock() defer w.mu.Unlock() w.writeHeader(statusCode) } // writeHeader persists the status code for span attribution, and propagates // the call to the underlying ResponseWriter. // It does not acquire a lock, and therefore assumes that is being handled by a // parent method. func (w *RespWriterWrapper) writeHeader(statusCode int) { if !w.wroteHeader { w.wroteHeader = true w.statusCode = statusCode } w.ResponseWriter.WriteHeader(statusCode) } // Flush implements [http.Flusher]. func (w *RespWriterWrapper) Flush() { w.mu.Lock() defer w.mu.Unlock() if !w.wroteHeader { w.writeHeader(http.StatusOK) } if f, ok := w.ResponseWriter.(http.Flusher); ok { f.Flush() } } // BytesWritten returns the number of bytes written. func (w *RespWriterWrapper) BytesWritten() int64 { w.mu.RLock() defer w.mu.RUnlock() return w.written } // BytesWritten returns the HTTP status code that was sent. func (w *RespWriterWrapper) StatusCode() int { w.mu.RLock() defer w.mu.RUnlock() return w.statusCode } // Error returns the last error. func (w *RespWriterWrapper) Error() error { w.mu.RLock() defer w.mu.RUnlock() return w.err } resp_writer_wrapper_test.go000066400000000000000000000027431470323427300420520ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/internal/request// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package request import ( "net/http" "net/http/httptest" "testing" "github.com/stretchr/testify/assert" ) func TestRespWriterWriteHeader(t *testing.T) { rw := NewRespWriterWrapper(&httptest.ResponseRecorder{}, func(int64) {}) rw.WriteHeader(http.StatusTeapot) assert.Equal(t, http.StatusTeapot, rw.statusCode) assert.True(t, rw.wroteHeader) rw.WriteHeader(http.StatusGone) assert.Equal(t, http.StatusTeapot, rw.statusCode) } func TestRespWriterFlush(t *testing.T) { rw := NewRespWriterWrapper(&httptest.ResponseRecorder{}, func(int64) {}) rw.Flush() assert.Equal(t, http.StatusOK, rw.statusCode) assert.True(t, rw.wroteHeader) } type nonFlushableResponseWriter struct{} func (_ nonFlushableResponseWriter) Header() http.Header { return http.Header{} } func (_ nonFlushableResponseWriter) Write([]byte) (int, error) { return 0, nil } func (_ nonFlushableResponseWriter) WriteHeader(int) {} func TestRespWriterFlushNoFlusher(t *testing.T) { rw := NewRespWriterWrapper(nonFlushableResponseWriter{}, func(int64) {}) rw.Flush() assert.Equal(t, http.StatusOK, rw.statusCode) assert.True(t, rw.wroteHeader) } func TestConcurrentRespWriterWrapper(t *testing.T) { rw := NewRespWriterWrapper(&httptest.ResponseRecorder{}, func(int64) {}) go func() { _, _ = rw.Write([]byte("hello world")) }() assert.NotNil(t, rw.BytesWritten()) assert.NotNil(t, rw.StatusCode()) assert.NoError(t, rw.Error()) } open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/internal/semconv/000077500000000000000000000000001470323427300344125ustar00rootroot00000000000000bench_test.go000066400000000000000000000021071470323427300370000ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/internal/semconv// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv import ( "net/http" "net/url" "testing" "go.opentelemetry.io/otel/attribute" ) var benchHTTPServerRequestResults []attribute.KeyValue // BenchmarkHTTPServerRequest allows comparison between different version of the HTTP server. // To use an alternative start this test with OTEL_SEMCONV_STABILITY_OPT_IN set to the // version under test. func BenchmarkHTTPServerRequest(b *testing.B) { // Request was generated from TestHTTPServerRequest request. req := &http.Request{ Method: http.MethodGet, URL: &url.URL{ Path: "/", }, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, Header: http.Header{ "User-Agent": []string{"Go-http-client/1.1"}, "Accept-Encoding": []string{"gzip"}, }, Body: http.NoBody, Host: "127.0.0.1:39093", RemoteAddr: "127.0.0.1:38738", RequestURI: "/", } serv := NewHTTPServer(nil) b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { benchHTTPServerRequestResults = serv.RequestTraceAttrs("", req) } } common_test.go000066400000000000000000000121621470323427300372130ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/internal/semconv// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv import ( "net/http" "net/http/httptest" "net/url" "strconv" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" ) type testServerReq struct { hostname string serverPort int peerAddr string peerPort int clientIP string } func testTraceRequest(t *testing.T, serv HTTPServer, want func(testServerReq) []attribute.KeyValue) { t.Helper() got := make(chan *http.Request, 1) handler := func(w http.ResponseWriter, r *http.Request) { got <- r close(got) w.WriteHeader(http.StatusOK) } srv := httptest.NewServer(http.HandlerFunc(handler)) defer srv.Close() srvURL, err := url.Parse(srv.URL) require.NoError(t, err) srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) require.NoError(t, err) resp, err := srv.Client().Get(srv.URL) require.NoError(t, err) require.NoError(t, resp.Body.Close()) req := <-got peer, peerPort := splitHostPort(req.RemoteAddr) const user = "alice" req.SetBasicAuth(user, "pswrd") const clientIP = "127.0.0.5" req.Header.Add("X-Forwarded-For", clientIP) srvReq := testServerReq{ hostname: srvURL.Hostname(), serverPort: int(srvPort), peerAddr: peer, peerPort: peerPort, clientIP: clientIP, } assert.ElementsMatch(t, want(srvReq), serv.RequestTraceAttrs("", req)) } func TestHTTPClientStatus(t *testing.T) { tests := []struct { code int stat codes.Code msg bool }{ {0, codes.Error, true}, {http.StatusContinue, codes.Unset, false}, {http.StatusSwitchingProtocols, codes.Unset, false}, {http.StatusProcessing, codes.Unset, false}, {http.StatusEarlyHints, codes.Unset, false}, {http.StatusOK, codes.Unset, false}, {http.StatusCreated, codes.Unset, false}, {http.StatusAccepted, codes.Unset, false}, {http.StatusNonAuthoritativeInfo, codes.Unset, false}, {http.StatusNoContent, codes.Unset, false}, {http.StatusResetContent, codes.Unset, false}, {http.StatusPartialContent, codes.Unset, false}, {http.StatusMultiStatus, codes.Unset, false}, {http.StatusAlreadyReported, codes.Unset, false}, {http.StatusIMUsed, codes.Unset, false}, {http.StatusMultipleChoices, codes.Unset, false}, {http.StatusMovedPermanently, codes.Unset, false}, {http.StatusFound, codes.Unset, false}, {http.StatusSeeOther, codes.Unset, false}, {http.StatusNotModified, codes.Unset, false}, {http.StatusUseProxy, codes.Unset, false}, {306, codes.Unset, false}, {http.StatusTemporaryRedirect, codes.Unset, false}, {http.StatusPermanentRedirect, codes.Unset, false}, {http.StatusBadRequest, codes.Error, false}, {http.StatusUnauthorized, codes.Error, false}, {http.StatusPaymentRequired, codes.Error, false}, {http.StatusForbidden, codes.Error, false}, {http.StatusNotFound, codes.Error, false}, {http.StatusMethodNotAllowed, codes.Error, false}, {http.StatusNotAcceptable, codes.Error, false}, {http.StatusProxyAuthRequired, codes.Error, false}, {http.StatusRequestTimeout, codes.Error, false}, {http.StatusConflict, codes.Error, false}, {http.StatusGone, codes.Error, false}, {http.StatusLengthRequired, codes.Error, false}, {http.StatusPreconditionFailed, codes.Error, false}, {http.StatusRequestEntityTooLarge, codes.Error, false}, {http.StatusRequestURITooLong, codes.Error, false}, {http.StatusUnsupportedMediaType, codes.Error, false}, {http.StatusRequestedRangeNotSatisfiable, codes.Error, false}, {http.StatusExpectationFailed, codes.Error, false}, {http.StatusTeapot, codes.Error, false}, {http.StatusMisdirectedRequest, codes.Error, false}, {http.StatusUnprocessableEntity, codes.Error, false}, {http.StatusLocked, codes.Error, false}, {http.StatusFailedDependency, codes.Error, false}, {http.StatusTooEarly, codes.Error, false}, {http.StatusUpgradeRequired, codes.Error, false}, {http.StatusPreconditionRequired, codes.Error, false}, {http.StatusTooManyRequests, codes.Error, false}, {http.StatusRequestHeaderFieldsTooLarge, codes.Error, false}, {http.StatusUnavailableForLegalReasons, codes.Error, false}, {499, codes.Error, false}, {http.StatusInternalServerError, codes.Error, false}, {http.StatusNotImplemented, codes.Error, false}, {http.StatusBadGateway, codes.Error, false}, {http.StatusServiceUnavailable, codes.Error, false}, {http.StatusGatewayTimeout, codes.Error, false}, {http.StatusHTTPVersionNotSupported, codes.Error, false}, {http.StatusVariantAlsoNegotiates, codes.Error, false}, {http.StatusInsufficientStorage, codes.Error, false}, {http.StatusLoopDetected, codes.Error, false}, {http.StatusNotExtended, codes.Error, false}, {http.StatusNetworkAuthenticationRequired, codes.Error, false}, {600, codes.Error, true}, } for _, test := range tests { t.Run(strconv.Itoa(test.code), func(t *testing.T) { c, msg := HTTPClient{}.Status(test.code) assert.Equal(t, test.stat, c) if test.msg && msg == "" { t.Errorf("expected non-empty message for %d", test.code) } else if !test.msg && msg != "" { t.Errorf("expected empty message for %d, got: %s", test.code, msg) } }) } } env.go000066400000000000000000000156071470323427300354630ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/internal/semconv// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv" import ( "context" "fmt" "net/http" "os" "strings" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/metric" ) type ResponseTelemetry struct { StatusCode int ReadBytes int64 ReadError error WriteBytes int64 WriteError error } type HTTPServer struct { duplicate bool // Old metrics requestBytesCounter metric.Int64Counter responseBytesCounter metric.Int64Counter serverLatencyMeasure metric.Float64Histogram } // RequestTraceAttrs returns trace attributes for an HTTP request received by a // server. // // The server must be the primary server name if it is known. For example this // would be the ServerName directive // (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache // server, and the server_name directive // (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an // nginx server. More generically, the primary server name would be the host // header value that matches the default virtual host of an HTTP server. It // should include the host identifier and if a port is used to route to the // server that port identifier should be included as an appropriate port // suffix. // // If the primary server name is not known, server should be an empty string. // The req Host will be used to determine the server instead. func (s HTTPServer) RequestTraceAttrs(server string, req *http.Request) []attribute.KeyValue { if s.duplicate { return append(oldHTTPServer{}.RequestTraceAttrs(server, req), newHTTPServer{}.RequestTraceAttrs(server, req)...) } return oldHTTPServer{}.RequestTraceAttrs(server, req) } // ResponseTraceAttrs returns trace attributes for telemetry from an HTTP response. // // If any of the fields in the ResponseTelemetry are not set the attribute will be omitted. func (s HTTPServer) ResponseTraceAttrs(resp ResponseTelemetry) []attribute.KeyValue { if s.duplicate { return append(oldHTTPServer{}.ResponseTraceAttrs(resp), newHTTPServer{}.ResponseTraceAttrs(resp)...) } return oldHTTPServer{}.ResponseTraceAttrs(resp) } // Route returns the attribute for the route. func (s HTTPServer) Route(route string) attribute.KeyValue { return oldHTTPServer{}.Route(route) } // Status returns a span status code and message for an HTTP status code // value returned by a server. Status codes in the 400-499 range are not // returned as errors. func (s HTTPServer) Status(code int) (codes.Code, string) { if code < 100 || code >= 600 { return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) } if code >= 500 { return codes.Error, "" } return codes.Unset, "" } type ServerMetricData struct { ServerName string ResponseSize int64 MetricData MetricAttributes } type MetricAttributes struct { Req *http.Request StatusCode int AdditionalAttributes []attribute.KeyValue } type MetricData struct { RequestSize int64 ElapsedTime float64 } func (s HTTPServer) RecordMetrics(ctx context.Context, md ServerMetricData) { if s.requestBytesCounter == nil || s.responseBytesCounter == nil || s.serverLatencyMeasure == nil { // This will happen if an HTTPServer{} is used insted of NewHTTPServer. return } attributes := oldHTTPServer{}.MetricAttributes(md.ServerName, md.Req, md.StatusCode, md.AdditionalAttributes) o := metric.WithAttributeSet(attribute.NewSet(attributes...)) addOpts := []metric.AddOption{o} s.requestBytesCounter.Add(ctx, md.RequestSize, addOpts...) s.responseBytesCounter.Add(ctx, md.ResponseSize, addOpts...) s.serverLatencyMeasure.Record(ctx, md.ElapsedTime, o) // TODO: Duplicate Metrics } func NewHTTPServer(meter metric.Meter) HTTPServer { env := strings.ToLower(os.Getenv("OTEL_SEMCONV_STABILITY_OPT_IN")) duplicate := env == "http/dup" server := HTTPServer{ duplicate: duplicate, } server.requestBytesCounter, server.responseBytesCounter, server.serverLatencyMeasure = oldHTTPServer{}.createMeasures(meter) return server } type HTTPClient struct { duplicate bool // old metrics requestBytesCounter metric.Int64Counter responseBytesCounter metric.Int64Counter latencyMeasure metric.Float64Histogram } func NewHTTPClient(meter metric.Meter) HTTPClient { env := strings.ToLower(os.Getenv("OTEL_SEMCONV_STABILITY_OPT_IN")) client := HTTPClient{ duplicate: env == "http/dup", } client.requestBytesCounter, client.responseBytesCounter, client.latencyMeasure = oldHTTPClient{}.createMeasures(meter) return client } // RequestTraceAttrs returns attributes for an HTTP request made by a client. func (c HTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue { if c.duplicate { return append(oldHTTPClient{}.RequestTraceAttrs(req), newHTTPClient{}.RequestTraceAttrs(req)...) } return oldHTTPClient{}.RequestTraceAttrs(req) } // ResponseTraceAttrs returns metric attributes for an HTTP request made by a client. func (c HTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue { if c.duplicate { return append(oldHTTPClient{}.ResponseTraceAttrs(resp), newHTTPClient{}.ResponseTraceAttrs(resp)...) } return oldHTTPClient{}.ResponseTraceAttrs(resp) } func (c HTTPClient) Status(code int) (codes.Code, string) { if code < 100 || code >= 600 { return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) } if code >= 400 { return codes.Error, "" } return codes.Unset, "" } func (c HTTPClient) ErrorType(err error) attribute.KeyValue { if c.duplicate { return newHTTPClient{}.ErrorType(err) } return attribute.KeyValue{} } type MetricOpts struct { measurement metric.MeasurementOption addOptions metric.AddOption } func (o MetricOpts) MeasurementOption() metric.MeasurementOption { return o.measurement } func (o MetricOpts) AddOptions() metric.AddOption { return o.addOptions } func (c HTTPClient) MetricOptions(ma MetricAttributes) MetricOpts { attributes := oldHTTPClient{}.MetricAttributes(ma.Req, ma.StatusCode, ma.AdditionalAttributes) // TODO: Duplicate Metrics set := metric.WithAttributeSet(attribute.NewSet(attributes...)) return MetricOpts{ measurement: set, addOptions: set, } } func (s HTTPClient) RecordMetrics(ctx context.Context, md MetricData, opts MetricOpts) { if s.requestBytesCounter == nil || s.latencyMeasure == nil { // This will happen if an HTTPClient{} is used insted of NewHTTPClient(). return } s.requestBytesCounter.Add(ctx, md.RequestSize, opts.AddOptions()) s.latencyMeasure.Record(ctx, md.ElapsedTime, opts.MeasurementOption()) // TODO: Duplicate Metrics } func (s HTTPClient) RecordResponseSize(ctx context.Context, responseData int64, opts metric.AddOption) { if s.responseBytesCounter == nil { // This will happen if an HTTPClient{} is used insted of NewHTTPClient(). return } s.responseBytesCounter.Add(ctx, responseData, opts) // TODO: Duplicate Metrics } env_test.go000066400000000000000000000056761470323427300365270ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/internal/semconv// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv import ( "context" "net/http" "testing" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/metric/embedded" "go.opentelemetry.io/otel/metric/noop" ) func TestHTTPServerDoesNotPanic(t *testing.T) { testCases := []struct { name string server HTTPServer }{ { name: "empty", server: HTTPServer{}, }, { name: "nil meter", server: NewHTTPServer(nil), }, { name: "with Meter", server: NewHTTPServer(noop.Meter{}), }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { require.NotPanics(t, func() { req, err := http.NewRequest("GET", "http://example.com", nil) require.NoError(t, err) _ = tt.server.RequestTraceAttrs("stuff", req) _ = tt.server.ResponseTraceAttrs(ResponseTelemetry{StatusCode: 200}) tt.server.RecordMetrics(context.Background(), ServerMetricData{ ServerName: "stuff", MetricAttributes: MetricAttributes{ Req: req, }, }) }) }) } } func TestHTTPClientDoesNotPanic(t *testing.T) { testCases := []struct { name string client HTTPClient }{ { name: "empty", client: HTTPClient{}, }, { name: "nil meter", client: NewHTTPClient(nil), }, { name: "with Meter", client: NewHTTPClient(noop.Meter{}), }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { require.NotPanics(t, func() { req, err := http.NewRequest("GET", "http://example.com", nil) require.NoError(t, err) _ = tt.client.RequestTraceAttrs(req) _ = tt.client.ResponseTraceAttrs(&http.Response{StatusCode: 200}) opts := tt.client.MetricOptions(MetricAttributes{ Req: req, StatusCode: 200, }) tt.client.RecordResponseSize(context.Background(), 40, opts.AddOptions()) tt.client.RecordMetrics(context.Background(), MetricData{ RequestSize: 20, ElapsedTime: 1, }, opts) }) }) } } type testInst struct { embedded.Int64Counter embedded.Float64Histogram intValue int64 floatValue float64 attributes []attribute.KeyValue } func (t *testInst) Add(ctx context.Context, incr int64, options ...metric.AddOption) { t.intValue = incr cfg := metric.NewAddConfig(options) attr := cfg.Attributes() t.attributes = attr.ToSlice() } func (t *testInst) Record(ctx context.Context, value float64, options ...metric.RecordOption) { t.floatValue = value cfg := metric.NewRecordConfig(options) attr := cfg.Attributes() t.attributes = attr.ToSlice() } func NewTestHTTPServer() HTTPServer { return HTTPServer{ requestBytesCounter: &testInst{}, responseBytesCounter: &testInst{}, serverLatencyMeasure: &testInst{}, } } func NewTestHTTPClient() HTTPClient { return HTTPClient{ requestBytesCounter: &testInst{}, responseBytesCounter: &testInst{}, latencyMeasure: &testInst{}, } } httpconv.go000066400000000000000000000214311470323427300365300ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/internal/semconv// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv" import ( "fmt" "net/http" "reflect" "strconv" "strings" "go.opentelemetry.io/otel/attribute" semconvNew "go.opentelemetry.io/otel/semconv/v1.26.0" ) type newHTTPServer struct{} // TraceRequest returns trace attributes for an HTTP request received by a // server. // // The server must be the primary server name if it is known. For example this // would be the ServerName directive // (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache // server, and the server_name directive // (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an // nginx server. More generically, the primary server name would be the host // header value that matches the default virtual host of an HTTP server. It // should include the host identifier and if a port is used to route to the // server that port identifier should be included as an appropriate port // suffix. // // If the primary server name is not known, server should be an empty string. // The req Host will be used to determine the server instead. func (n newHTTPServer) RequestTraceAttrs(server string, req *http.Request) []attribute.KeyValue { count := 3 // ServerAddress, Method, Scheme var host string var p int if server == "" { host, p = splitHostPort(req.Host) } else { // Prioritize the primary server name. host, p = splitHostPort(server) if p < 0 { _, p = splitHostPort(req.Host) } } hostPort := requiredHTTPPort(req.TLS != nil, p) if hostPort > 0 { count++ } method, methodOriginal := n.method(req.Method) if methodOriginal != (attribute.KeyValue{}) { count++ } scheme := n.scheme(req.TLS != nil) if peer, peerPort := splitHostPort(req.RemoteAddr); peer != "" { // The Go HTTP server sets RemoteAddr to "IP:port", this will not be a // file-path that would be interpreted with a sock family. count++ if peerPort > 0 { count++ } } useragent := req.UserAgent() if useragent != "" { count++ } clientIP := serverClientIP(req.Header.Get("X-Forwarded-For")) if clientIP != "" { count++ } if req.URL != nil && req.URL.Path != "" { count++ } protoName, protoVersion := netProtocol(req.Proto) if protoName != "" && protoName != "http" { count++ } if protoVersion != "" { count++ } attrs := make([]attribute.KeyValue, 0, count) attrs = append(attrs, semconvNew.ServerAddress(host), method, scheme, ) if hostPort > 0 { attrs = append(attrs, semconvNew.ServerPort(hostPort)) } if methodOriginal != (attribute.KeyValue{}) { attrs = append(attrs, methodOriginal) } if peer, peerPort := splitHostPort(req.RemoteAddr); peer != "" { // The Go HTTP server sets RemoteAddr to "IP:port", this will not be a // file-path that would be interpreted with a sock family. attrs = append(attrs, semconvNew.NetworkPeerAddress(peer)) if peerPort > 0 { attrs = append(attrs, semconvNew.NetworkPeerPort(peerPort)) } } if useragent := req.UserAgent(); useragent != "" { attrs = append(attrs, semconvNew.UserAgentOriginal(useragent)) } if clientIP != "" { attrs = append(attrs, semconvNew.ClientAddress(clientIP)) } if req.URL != nil && req.URL.Path != "" { attrs = append(attrs, semconvNew.URLPath(req.URL.Path)) } if protoName != "" && protoName != "http" { attrs = append(attrs, semconvNew.NetworkProtocolName(protoName)) } if protoVersion != "" { attrs = append(attrs, semconvNew.NetworkProtocolVersion(protoVersion)) } return attrs } func (n newHTTPServer) method(method string) (attribute.KeyValue, attribute.KeyValue) { if method == "" { return semconvNew.HTTPRequestMethodGet, attribute.KeyValue{} } if attr, ok := methodLookup[method]; ok { return attr, attribute.KeyValue{} } orig := semconvNew.HTTPRequestMethodOriginal(method) if attr, ok := methodLookup[strings.ToUpper(method)]; ok { return attr, orig } return semconvNew.HTTPRequestMethodGet, orig } func (n newHTTPServer) scheme(https bool) attribute.KeyValue { // nolint:revive if https { return semconvNew.URLScheme("https") } return semconvNew.URLScheme("http") } // TraceResponse returns trace attributes for telemetry from an HTTP response. // // If any of the fields in the ResponseTelemetry are not set the attribute will be omitted. func (n newHTTPServer) ResponseTraceAttrs(resp ResponseTelemetry) []attribute.KeyValue { var count int if resp.ReadBytes > 0 { count++ } if resp.WriteBytes > 0 { count++ } if resp.StatusCode > 0 { count++ } attributes := make([]attribute.KeyValue, 0, count) if resp.ReadBytes > 0 { attributes = append(attributes, semconvNew.HTTPRequestBodySize(int(resp.ReadBytes)), ) } if resp.WriteBytes > 0 { attributes = append(attributes, semconvNew.HTTPResponseBodySize(int(resp.WriteBytes)), ) } if resp.StatusCode > 0 { attributes = append(attributes, semconvNew.HTTPResponseStatusCode(resp.StatusCode), ) } return attributes } // Route returns the attribute for the route. func (n newHTTPServer) Route(route string) attribute.KeyValue { return semconvNew.HTTPRoute(route) } type newHTTPClient struct{} // RequestTraceAttrs returns trace attributes for an HTTP request made by a client. func (n newHTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue { /* below attributes are returned: - http.request.method - http.request.method.original - url.full - server.address - server.port - network.protocol.name - network.protocol.version */ numOfAttributes := 3 // URL, server address, proto, and method. var urlHost string if req.URL != nil { urlHost = req.URL.Host } var requestHost string var requestPort int for _, hostport := range []string{urlHost, req.Header.Get("Host")} { requestHost, requestPort = splitHostPort(hostport) if requestHost != "" || requestPort > 0 { break } } eligiblePort := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", requestPort) if eligiblePort > 0 { numOfAttributes++ } useragent := req.UserAgent() if useragent != "" { numOfAttributes++ } protoName, protoVersion := netProtocol(req.Proto) if protoName != "" && protoName != "http" { numOfAttributes++ } if protoVersion != "" { numOfAttributes++ } method, originalMethod := n.method(req.Method) if originalMethod != (attribute.KeyValue{}) { numOfAttributes++ } attrs := make([]attribute.KeyValue, 0, numOfAttributes) attrs = append(attrs, method) if originalMethod != (attribute.KeyValue{}) { attrs = append(attrs, originalMethod) } var u string if req.URL != nil { // Remove any username/password info that may be in the URL. userinfo := req.URL.User req.URL.User = nil u = req.URL.String() // Restore any username/password info that was removed. req.URL.User = userinfo } attrs = append(attrs, semconvNew.URLFull(u)) attrs = append(attrs, semconvNew.ServerAddress(requestHost)) if eligiblePort > 0 { attrs = append(attrs, semconvNew.ServerPort(eligiblePort)) } if protoName != "" && protoName != "http" { attrs = append(attrs, semconvNew.NetworkProtocolName(protoName)) } if protoVersion != "" { attrs = append(attrs, semconvNew.NetworkProtocolVersion(protoVersion)) } return attrs } // ResponseTraceAttrs returns trace attributes for an HTTP response made by a client. func (n newHTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue { /* below attributes are returned: - http.response.status_code - error.type */ var count int if resp.StatusCode > 0 { count++ } if isErrorStatusCode(resp.StatusCode) { count++ } attrs := make([]attribute.KeyValue, 0, count) if resp.StatusCode > 0 { attrs = append(attrs, semconvNew.HTTPResponseStatusCode(resp.StatusCode)) } if isErrorStatusCode(resp.StatusCode) { errorType := strconv.Itoa(resp.StatusCode) attrs = append(attrs, semconvNew.ErrorTypeKey.String(errorType)) } return attrs } func (n newHTTPClient) ErrorType(err error) attribute.KeyValue { t := reflect.TypeOf(err) var value string if t.PkgPath() == "" && t.Name() == "" { // Likely a builtin type. value = t.String() } else { value = fmt.Sprintf("%s.%s", t.PkgPath(), t.Name()) } if value == "" { return semconvNew.ErrorTypeOther } return semconvNew.ErrorTypeKey.String(value) } func (n newHTTPClient) method(method string) (attribute.KeyValue, attribute.KeyValue) { if method == "" { return semconvNew.HTTPRequestMethodGet, attribute.KeyValue{} } if attr, ok := methodLookup[method]; ok { return attr, attribute.KeyValue{} } orig := semconvNew.HTTPRequestMethodOriginal(method) if attr, ok := methodLookup[strings.ToUpper(method)]; ok { return attr, orig } return semconvNew.HTTPRequestMethodGet, orig } func isErrorStatusCode(code int) bool { return code >= 400 || code < 100 } httpconv_test.go000066400000000000000000000156521470323427300375770ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/internal/semconv// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv import ( "errors" "fmt" "net/http" "net/http/httptest" "strings" "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/attribute" ) func TestNewTraceRequest(t *testing.T) { t.Setenv("OTEL_SEMCONV_STABILITY_OPT_IN", "http/dup") serv := NewHTTPServer(nil) want := func(req testServerReq) []attribute.KeyValue { return []attribute.KeyValue{ attribute.String("http.request.method", "GET"), attribute.String("url.scheme", "http"), attribute.String("server.address", req.hostname), attribute.Int("server.port", req.serverPort), attribute.String("network.peer.address", req.peerAddr), attribute.Int("network.peer.port", req.peerPort), attribute.String("user_agent.original", "Go-http-client/1.1"), attribute.String("client.address", req.clientIP), attribute.String("network.protocol.version", "1.1"), attribute.String("url.path", "/"), attribute.String("http.method", "GET"), attribute.String("http.scheme", "http"), attribute.String("net.host.name", req.hostname), attribute.Int("net.host.port", req.serverPort), attribute.String("net.sock.peer.addr", req.peerAddr), attribute.Int("net.sock.peer.port", req.peerPort), attribute.String("user_agent.original", "Go-http-client/1.1"), attribute.String("http.client_ip", req.clientIP), attribute.String("net.protocol.version", "1.1"), attribute.String("http.target", "/"), } } testTraceRequest(t, serv, want) } func TestNewTraceResponse(t *testing.T) { testCases := []struct { name string resp ResponseTelemetry want []attribute.KeyValue }{ { name: "empty", resp: ResponseTelemetry{}, want: nil, }, { name: "no errors", resp: ResponseTelemetry{ StatusCode: 200, ReadBytes: 701, WriteBytes: 802, }, want: []attribute.KeyValue{ attribute.Int("http.request.body.size", 701), attribute.Int("http.response.body.size", 802), attribute.Int("http.response.status_code", 200), }, }, { name: "with errors", resp: ResponseTelemetry{ StatusCode: 200, ReadBytes: 701, ReadError: fmt.Errorf("read error"), WriteBytes: 802, WriteError: fmt.Errorf("write error"), }, want: []attribute.KeyValue{ attribute.Int("http.request.body.size", 701), attribute.Int("http.response.body.size", 802), attribute.Int("http.response.status_code", 200), }, }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { got := newHTTPServer{}.ResponseTraceAttrs(tt.resp) assert.ElementsMatch(t, tt.want, got) }) } } func TestNewMethod(t *testing.T) { testCases := []struct { method string n int want attribute.KeyValue wantOrig attribute.KeyValue }{ { method: http.MethodPost, n: 1, want: attribute.String("http.request.method", "POST"), }, { method: "Put", n: 2, want: attribute.String("http.request.method", "PUT"), wantOrig: attribute.String("http.request.method_original", "Put"), }, { method: "Unknown", n: 2, want: attribute.String("http.request.method", "GET"), wantOrig: attribute.String("http.request.method_original", "Unknown"), }, } for _, tt := range testCases { t.Run(tt.method, func(t *testing.T) { got, gotOrig := newHTTPServer{}.method(tt.method) assert.Equal(t, tt.want, got) assert.Equal(t, tt.wantOrig, gotOrig) }) } } func TestNewTraceRequest_Client(t *testing.T) { t.Setenv("OTEL_SEMCONV_STABILITY_OPT_IN", "http/dup") body := strings.NewReader("Hello, world!") url := "https://example.com:8888/foo/bar?stuff=morestuff" req := httptest.NewRequest("pOST", url, body) req.Header.Set("User-Agent", "go-test-agent") want := []attribute.KeyValue{ attribute.String("http.request.method", "POST"), attribute.String("http.request.method_original", "pOST"), attribute.String("http.method", "pOST"), attribute.String("url.full", url), attribute.String("http.url", url), attribute.String("server.address", "example.com"), attribute.Int("server.port", 8888), attribute.String("network.protocol.version", "1.1"), attribute.String("net.peer.name", "example.com"), attribute.Int("net.peer.port", 8888), attribute.String("user_agent.original", "go-test-agent"), attribute.Int("http.request_content_length", 13), } client := NewHTTPClient(nil) assert.ElementsMatch(t, want, client.RequestTraceAttrs(req)) } func TestNewTraceResponse_Client(t *testing.T) { t.Setenv("OTEL_SEMCONV_STABILITY_OPT_IN", "http/dup") testcases := []struct { resp http.Response want []attribute.KeyValue }{ {resp: http.Response{StatusCode: 200, ContentLength: 123}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 200), attribute.Int("http.status_code", 200), attribute.Int("http.response_content_length", 123)}}, {resp: http.Response{StatusCode: 404, ContentLength: 0}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 404), attribute.Int("http.status_code", 404), attribute.String("error.type", "404")}}, } for _, tt := range testcases { client := NewHTTPClient(nil) assert.ElementsMatch(t, tt.want, client.ResponseTraceAttrs(&tt.resp)) } } func TestClientRequest(t *testing.T) { body := strings.NewReader("Hello, world!") url := "https://example.com:8888/foo/bar?stuff=morestuff" req := httptest.NewRequest("pOST", url, body) req.Header.Set("User-Agent", "go-test-agent") want := []attribute.KeyValue{ attribute.String("http.request.method", "POST"), attribute.String("http.request.method_original", "pOST"), attribute.String("url.full", url), attribute.String("server.address", "example.com"), attribute.Int("server.port", 8888), attribute.String("network.protocol.version", "1.1"), } got := newHTTPClient{}.RequestTraceAttrs(req) assert.ElementsMatch(t, want, got) } func TestClientResponse(t *testing.T) { testcases := []struct { resp http.Response want []attribute.KeyValue }{ {resp: http.Response{StatusCode: 200, ContentLength: 123}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 200)}}, {resp: http.Response{StatusCode: 404, ContentLength: 0}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 404), attribute.String("error.type", "404")}}, } for _, tt := range testcases { got := newHTTPClient{}.ResponseTraceAttrs(&tt.resp) assert.ElementsMatch(t, tt.want, got) } } func TestRequestErrorType(t *testing.T) { testcases := []struct { err error want attribute.KeyValue }{ {err: errors.New("http: nil Request.URL"), want: attribute.String("error.type", "*errors.errorString")}, {err: customError{}, want: attribute.String("error.type", "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv.customError")}, } for _, tt := range testcases { got := newHTTPClient{}.ErrorType(tt.err) assert.Equal(t, tt.want, got) } } type customError struct{} func (customError) Error() string { return "custom error" } util.go000066400000000000000000000047501470323427300356450ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/internal/semconv// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv" import ( "net" "net/http" "strconv" "strings" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" semconvNew "go.opentelemetry.io/otel/semconv/v1.26.0" ) // splitHostPort splits a network address hostport of the form "host", // "host%zone", "[host]", "[host%zone], "host:port", "host%zone:port", // "[host]:port", "[host%zone]:port", or ":port" into host or host%zone and // port. // // An empty host is returned if it is not provided or unparsable. A negative // port is returned if it is not provided or unparsable. func splitHostPort(hostport string) (host string, port int) { port = -1 if strings.HasPrefix(hostport, "[") { addrEnd := strings.LastIndex(hostport, "]") if addrEnd < 0 { // Invalid hostport. return } if i := strings.LastIndex(hostport[addrEnd:], ":"); i < 0 { host = hostport[1:addrEnd] return } } else { if i := strings.LastIndex(hostport, ":"); i < 0 { host = hostport return } } host, pStr, err := net.SplitHostPort(hostport) if err != nil { return } p, err := strconv.ParseUint(pStr, 10, 16) if err != nil { return } return host, int(p) // nolint: gosec // Byte size checked 16 above. } func requiredHTTPPort(https bool, port int) int { // nolint:revive if https { if port > 0 && port != 443 { return port } } else { if port > 0 && port != 80 { return port } } return -1 } func serverClientIP(xForwardedFor string) string { if idx := strings.Index(xForwardedFor, ","); idx >= 0 { xForwardedFor = xForwardedFor[:idx] } return xForwardedFor } func netProtocol(proto string) (name string, version string) { name, version, _ = strings.Cut(proto, "/") name = strings.ToLower(name) return name, version } var methodLookup = map[string]attribute.KeyValue{ http.MethodConnect: semconvNew.HTTPRequestMethodConnect, http.MethodDelete: semconvNew.HTTPRequestMethodDelete, http.MethodGet: semconvNew.HTTPRequestMethodGet, http.MethodHead: semconvNew.HTTPRequestMethodHead, http.MethodOptions: semconvNew.HTTPRequestMethodOptions, http.MethodPatch: semconvNew.HTTPRequestMethodPatch, http.MethodPost: semconvNew.HTTPRequestMethodPost, http.MethodPut: semconvNew.HTTPRequestMethodPut, http.MethodTrace: semconvNew.HTTPRequestMethodTrace, } func handleErr(err error) { if err != nil { otel.Handle(err) } } util_test.go000066400000000000000000000020511470323427300366740ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/internal/semconv// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv import ( "testing" "github.com/stretchr/testify/assert" ) func TestSplitHostPort(t *testing.T) { tests := []struct { hostport string host string port int }{ {"", "", -1}, {":8080", "", 8080}, {"127.0.0.1", "127.0.0.1", -1}, {"www.example.com", "www.example.com", -1}, {"127.0.0.1%25en0", "127.0.0.1%25en0", -1}, {"[]", "", -1}, // Ensure this doesn't panic. {"[fe80::1", "", -1}, {"[fe80::1]", "fe80::1", -1}, {"[fe80::1%25en0]", "fe80::1%25en0", -1}, {"[fe80::1]:8080", "fe80::1", 8080}, {"[fe80::1]::", "", -1}, // Too many colons. {"127.0.0.1:", "127.0.0.1", -1}, {"127.0.0.1:port", "127.0.0.1", -1}, {"127.0.0.1:8080", "127.0.0.1", 8080}, {"www.example.com:8080", "www.example.com", 8080}, {"127.0.0.1%25en0:8080", "127.0.0.1%25en0", 8080}, } for _, test := range tests { h, p := splitHostPort(test.hostport) assert.Equal(t, test.host, h, test.hostport) assert.Equal(t, test.port, p, test.hostport) } } v1.20.0.go000066400000000000000000000207311470323427300355710ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/internal/semconv// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv" import ( "errors" "io" "net/http" "slices" "strings" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/metric/noop" semconv "go.opentelemetry.io/otel/semconv/v1.20.0" ) type oldHTTPServer struct{} // RequestTraceAttrs returns trace attributes for an HTTP request received by a // server. // // The server must be the primary server name if it is known. For example this // would be the ServerName directive // (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache // server, and the server_name directive // (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an // nginx server. More generically, the primary server name would be the host // header value that matches the default virtual host of an HTTP server. It // should include the host identifier and if a port is used to route to the // server that port identifier should be included as an appropriate port // suffix. // // If the primary server name is not known, server should be an empty string. // The req Host will be used to determine the server instead. func (o oldHTTPServer) RequestTraceAttrs(server string, req *http.Request) []attribute.KeyValue { return semconvutil.HTTPServerRequest(server, req) } // ResponseTraceAttrs returns trace attributes for telemetry from an HTTP response. // // If any of the fields in the ResponseTelemetry are not set the attribute will be omitted. func (o oldHTTPServer) ResponseTraceAttrs(resp ResponseTelemetry) []attribute.KeyValue { attributes := []attribute.KeyValue{} if resp.ReadBytes > 0 { attributes = append(attributes, semconv.HTTPRequestContentLength(int(resp.ReadBytes))) } if resp.ReadError != nil && !errors.Is(resp.ReadError, io.EOF) { // This is not in the semantic conventions, but is historically provided attributes = append(attributes, attribute.String("http.read_error", resp.ReadError.Error())) } if resp.WriteBytes > 0 { attributes = append(attributes, semconv.HTTPResponseContentLength(int(resp.WriteBytes))) } if resp.StatusCode > 0 { attributes = append(attributes, semconv.HTTPStatusCode(resp.StatusCode)) } if resp.WriteError != nil && !errors.Is(resp.WriteError, io.EOF) { // This is not in the semantic conventions, but is historically provided attributes = append(attributes, attribute.String("http.write_error", resp.WriteError.Error())) } return attributes } // Route returns the attribute for the route. func (o oldHTTPServer) Route(route string) attribute.KeyValue { return semconv.HTTPRoute(route) } // HTTPStatusCode returns the attribute for the HTTP status code. // This is a temporary function needed by metrics. This will be removed when MetricsRequest is added. func HTTPStatusCode(status int) attribute.KeyValue { return semconv.HTTPStatusCode(status) } // Server HTTP metrics. const ( serverRequestSize = "http.server.request.size" // Incoming request bytes total serverResponseSize = "http.server.response.size" // Incoming response bytes total serverDuration = "http.server.duration" // Incoming end to end duration, milliseconds ) func (h oldHTTPServer) createMeasures(meter metric.Meter) (metric.Int64Counter, metric.Int64Counter, metric.Float64Histogram) { if meter == nil { return noop.Int64Counter{}, noop.Int64Counter{}, noop.Float64Histogram{} } var err error requestBytesCounter, err := meter.Int64Counter( serverRequestSize, metric.WithUnit("By"), metric.WithDescription("Measures the size of HTTP request messages."), ) handleErr(err) responseBytesCounter, err := meter.Int64Counter( serverResponseSize, metric.WithUnit("By"), metric.WithDescription("Measures the size of HTTP response messages."), ) handleErr(err) serverLatencyMeasure, err := meter.Float64Histogram( serverDuration, metric.WithUnit("ms"), metric.WithDescription("Measures the duration of inbound HTTP requests."), ) handleErr(err) return requestBytesCounter, responseBytesCounter, serverLatencyMeasure } func (o oldHTTPServer) MetricAttributes(server string, req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue { n := len(additionalAttributes) + 3 var host string var p int if server == "" { host, p = splitHostPort(req.Host) } else { // Prioritize the primary server name. host, p = splitHostPort(server) if p < 0 { _, p = splitHostPort(req.Host) } } hostPort := requiredHTTPPort(req.TLS != nil, p) if hostPort > 0 { n++ } protoName, protoVersion := netProtocol(req.Proto) if protoName != "" { n++ } if protoVersion != "" { n++ } if statusCode > 0 { n++ } attributes := slices.Grow(additionalAttributes, n) attributes = append(attributes, standardizeHTTPMethodMetric(req.Method), o.scheme(req.TLS != nil), semconv.NetHostName(host)) if hostPort > 0 { attributes = append(attributes, semconv.NetHostPort(hostPort)) } if protoName != "" { attributes = append(attributes, semconv.NetProtocolName(protoName)) } if protoVersion != "" { attributes = append(attributes, semconv.NetProtocolVersion(protoVersion)) } if statusCode > 0 { attributes = append(attributes, semconv.HTTPStatusCode(statusCode)) } return attributes } func (o oldHTTPServer) scheme(https bool) attribute.KeyValue { // nolint:revive if https { return semconv.HTTPSchemeHTTPS } return semconv.HTTPSchemeHTTP } type oldHTTPClient struct{} func (o oldHTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue { return semconvutil.HTTPClientRequest(req) } func (o oldHTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue { return semconvutil.HTTPClientResponse(resp) } func (o oldHTTPClient) MetricAttributes(req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue { /* The following semantic conventions are returned if present: http.method string http.status_code int net.peer.name string net.peer.port int */ n := 2 // method, peer name. var h string if req.URL != nil { h = req.URL.Host } var requestHost string var requestPort int for _, hostport := range []string{h, req.Header.Get("Host")} { requestHost, requestPort = splitHostPort(hostport) if requestHost != "" || requestPort > 0 { break } } port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", requestPort) if port > 0 { n++ } if statusCode > 0 { n++ } attributes := slices.Grow(additionalAttributes, n) attributes = append(attributes, standardizeHTTPMethodMetric(req.Method), semconv.NetPeerName(requestHost), ) if port > 0 { attributes = append(attributes, semconv.NetPeerPort(port)) } if statusCode > 0 { attributes = append(attributes, semconv.HTTPStatusCode(statusCode)) } return attributes } // Client HTTP metrics. const ( clientRequestSize = "http.client.request.size" // Incoming request bytes total clientResponseSize = "http.client.response.size" // Incoming response bytes total clientDuration = "http.client.duration" // Incoming end to end duration, milliseconds ) func (o oldHTTPClient) createMeasures(meter metric.Meter) (metric.Int64Counter, metric.Int64Counter, metric.Float64Histogram) { if meter == nil { return noop.Int64Counter{}, noop.Int64Counter{}, noop.Float64Histogram{} } requestBytesCounter, err := meter.Int64Counter( clientRequestSize, metric.WithUnit("By"), metric.WithDescription("Measures the size of HTTP request messages."), ) handleErr(err) responseBytesCounter, err := meter.Int64Counter( clientResponseSize, metric.WithUnit("By"), metric.WithDescription("Measures the size of HTTP response messages."), ) handleErr(err) latencyMeasure, err := meter.Float64Histogram( clientDuration, metric.WithUnit("ms"), metric.WithDescription("Measures the duration of outbound HTTP requests."), ) handleErr(err) return requestBytesCounter, responseBytesCounter, latencyMeasure } func standardizeHTTPMethodMetric(method string) attribute.KeyValue { method = strings.ToUpper(method) switch method { case http.MethodConnect, http.MethodDelete, http.MethodGet, http.MethodHead, http.MethodOptions, http.MethodPatch, http.MethodPost, http.MethodPut, http.MethodTrace: default: method = "_OTHER" } return semconv.HTTPMethod(method) } v1.20.0_test.go000066400000000000000000000154731470323427300366370ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/internal/semconv// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconv import ( "context" "fmt" "net/http" "strings" "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/attribute" ) func TestV120TraceRequest(t *testing.T) { // Anything but "http" or "http/dup" works. t.Setenv("OTEL_SEMCONV_STABILITY_OPT_IN", "old") serv := NewHTTPServer(nil) want := func(req testServerReq) []attribute.KeyValue { return []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.scheme", "http"), attribute.String("net.host.name", req.hostname), attribute.Int("net.host.port", req.serverPort), attribute.String("net.sock.peer.addr", req.peerAddr), attribute.Int("net.sock.peer.port", req.peerPort), attribute.String("user_agent.original", "Go-http-client/1.1"), attribute.String("http.client_ip", req.clientIP), attribute.String("net.protocol.version", "1.1"), attribute.String("http.target", "/"), } } testTraceRequest(t, serv, want) } func TestV120TraceResponse(t *testing.T) { testCases := []struct { name string resp ResponseTelemetry want []attribute.KeyValue }{ { name: "empty", resp: ResponseTelemetry{}, want: nil, }, { name: "no errors", resp: ResponseTelemetry{ StatusCode: 200, ReadBytes: 701, WriteBytes: 802, }, want: []attribute.KeyValue{ attribute.Int("http.request_content_length", 701), attribute.Int("http.response_content_length", 802), attribute.Int("http.status_code", 200), }, }, { name: "with errors", resp: ResponseTelemetry{ StatusCode: 200, ReadBytes: 701, ReadError: fmt.Errorf("read error"), WriteBytes: 802, WriteError: fmt.Errorf("write error"), }, want: []attribute.KeyValue{ attribute.Int("http.request_content_length", 701), attribute.String("http.read_error", "read error"), attribute.Int("http.response_content_length", 802), attribute.String("http.write_error", "write error"), attribute.Int("http.status_code", 200), }, }, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { got := oldHTTPServer{}.ResponseTraceAttrs(tt.resp) assert.ElementsMatch(t, tt.want, got) }) } } func TestV120RecordMetrics(t *testing.T) { server := NewTestHTTPServer() req, err := http.NewRequest("POST", "http://example.com", nil) assert.NoError(t, err) server.RecordMetrics(context.Background(), ServerMetricData{ ServerName: "stuff", ResponseSize: 200, MetricAttributes: MetricAttributes{ Req: req, StatusCode: 301, AdditionalAttributes: []attribute.KeyValue{ attribute.String("key", "value"), }, }, MetricData: MetricData{ RequestSize: 100, ElapsedTime: 300, }, }) assert.Equal(t, int64(100), server.requestBytesCounter.(*testInst).intValue) assert.Equal(t, int64(200), server.responseBytesCounter.(*testInst).intValue) assert.Equal(t, float64(300), server.serverLatencyMeasure.(*testInst).floatValue) want := []attribute.KeyValue{ attribute.String("http.scheme", "http"), attribute.String("http.method", "POST"), attribute.Int64("http.status_code", 301), attribute.String("key", "value"), attribute.String("net.host.name", "stuff"), attribute.String("net.protocol.name", "http"), attribute.String("net.protocol.version", "1.1"), } assert.ElementsMatch(t, want, server.requestBytesCounter.(*testInst).attributes) assert.ElementsMatch(t, want, server.responseBytesCounter.(*testInst).attributes) assert.ElementsMatch(t, want, server.serverLatencyMeasure.(*testInst).attributes) } func TestV120ClientRequest(t *testing.T) { body := strings.NewReader("Hello, world!") url := "https://example.com:8888/foo/bar?stuff=morestuff" req, err := http.NewRequest("POST", url, body) assert.NoError(t, err) req.Header.Set("User-Agent", "go-test-agent") want := []attribute.KeyValue{ attribute.String("http.method", "POST"), attribute.String("http.url", url), attribute.String("net.peer.name", "example.com"), attribute.Int("net.peer.port", 8888), attribute.Int("http.request_content_length", body.Len()), attribute.String("user_agent.original", "go-test-agent"), } got := oldHTTPClient{}.RequestTraceAttrs(req) assert.ElementsMatch(t, want, got) } func TestV120ClientResponse(t *testing.T) { resp := http.Response{ StatusCode: 200, ContentLength: 123, } want := []attribute.KeyValue{ attribute.Int("http.response_content_length", 123), attribute.Int("http.status_code", 200), } got := oldHTTPClient{}.ResponseTraceAttrs(&resp) assert.ElementsMatch(t, want, got) } func TestV120ClientMetrics(t *testing.T) { client := NewTestHTTPClient() req, err := http.NewRequest("POST", "http://example.com", nil) assert.NoError(t, err) opts := client.MetricOptions(MetricAttributes{ Req: req, StatusCode: 301, AdditionalAttributes: []attribute.KeyValue{ attribute.String("key", "value"), }, }) ctx := context.Background() client.RecordResponseSize(ctx, 200, opts.AddOptions()) client.RecordMetrics(ctx, MetricData{ RequestSize: 100, ElapsedTime: 300, }, opts) assert.Equal(t, int64(100), client.requestBytesCounter.(*testInst).intValue) assert.Equal(t, int64(200), client.responseBytesCounter.(*testInst).intValue) assert.Equal(t, float64(300), client.latencyMeasure.(*testInst).floatValue) want := []attribute.KeyValue{ attribute.String("http.method", "POST"), attribute.Int64("http.status_code", 301), attribute.String("key", "value"), attribute.String("net.peer.name", "example.com"), } assert.ElementsMatch(t, want, client.requestBytesCounter.(*testInst).attributes) assert.ElementsMatch(t, want, client.responseBytesCounter.(*testInst).attributes) assert.ElementsMatch(t, want, client.latencyMeasure.(*testInst).attributes) } func TestStandardizeHTTPMethodMetric(t *testing.T) { testCases := []struct { method string want attribute.KeyValue }{ { method: "GET", want: attribute.String("http.method", "GET"), }, { method: "POST", want: attribute.String("http.method", "POST"), }, { method: "PUT", want: attribute.String("http.method", "PUT"), }, { method: "DELETE", want: attribute.String("http.method", "DELETE"), }, { method: "HEAD", want: attribute.String("http.method", "HEAD"), }, { method: "OPTIONS", want: attribute.String("http.method", "OPTIONS"), }, { method: "CONNECT", want: attribute.String("http.method", "CONNECT"), }, { method: "TRACE", want: attribute.String("http.method", "TRACE"), }, { method: "PATCH", want: attribute.String("http.method", "PATCH"), }, { method: "test", want: attribute.String("http.method", "_OTHER"), }, } for _, tt := range testCases { t.Run(tt.method, func(t *testing.T) { got := standardizeHTTPMethodMetric(tt.method) assert.Equal(t, tt.want, got) }) } } semconvutil/000077500000000000000000000000001470323427300352315ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/internalgen.go000066400000000000000000000013301470323427300363260ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/internal/semconvutil// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil" // Generate semconvutil package: //go:generate gotmpl --body=../../../../../../internal/shared/semconvutil/httpconv_test.go.tmpl "--data={}" --out=httpconv_test.go //go:generate gotmpl --body=../../../../../../internal/shared/semconvutil/httpconv.go.tmpl "--data={}" --out=httpconv.go //go:generate gotmpl --body=../../../../../../internal/shared/semconvutil/netconv_test.go.tmpl "--data={}" --out=netconv_test.go //go:generate gotmpl --body=../../../../../../internal/shared/semconvutil/netconv.go.tmpl "--data={}" --out=netconv.go httpconv.go000066400000000000000000000466011470323427300374340ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/internal/semconvutil// Code created by gotmpl. DO NOT MODIFY. // source: internal/shared/semconvutil/httpconv.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil" import ( "fmt" "net/http" "strings" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" semconv "go.opentelemetry.io/otel/semconv/v1.20.0" ) // HTTPClientResponse returns trace attributes for an HTTP response received by a // client from a server. It will return the following attributes if the related // values are defined in resp: "http.status.code", // "http.response_content_length". // // This does not add all OpenTelemetry required attributes for an HTTP event, // it assumes ClientRequest was used to create the span with a complete set of // attributes. If a complete set of attributes can be generated using the // request contained in resp. For example: // // append(HTTPClientResponse(resp), ClientRequest(resp.Request)...) func HTTPClientResponse(resp *http.Response) []attribute.KeyValue { return hc.ClientResponse(resp) } // HTTPClientRequest returns trace attributes for an HTTP request made by a client. // The following attributes are always returned: "http.url", "http.method", // "net.peer.name". The following attributes are returned if the related values // are defined in req: "net.peer.port", "user_agent.original", // "http.request_content_length". func HTTPClientRequest(req *http.Request) []attribute.KeyValue { return hc.ClientRequest(req) } // HTTPClientRequestMetrics returns metric attributes for an HTTP request made by a client. // The following attributes are always returned: "http.method", "net.peer.name". // The following attributes are returned if the // related values are defined in req: "net.peer.port". func HTTPClientRequestMetrics(req *http.Request) []attribute.KeyValue { return hc.ClientRequestMetrics(req) } // HTTPClientStatus returns a span status code and message for an HTTP status code // value received by a client. func HTTPClientStatus(code int) (codes.Code, string) { return hc.ClientStatus(code) } // HTTPServerRequest returns trace attributes for an HTTP request received by a // server. // // The server must be the primary server name if it is known. For example this // would be the ServerName directive // (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache // server, and the server_name directive // (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an // nginx server. More generically, the primary server name would be the host // header value that matches the default virtual host of an HTTP server. It // should include the host identifier and if a port is used to route to the // server that port identifier should be included as an appropriate port // suffix. // // If the primary server name is not known, server should be an empty string. // The req Host will be used to determine the server instead. // // The following attributes are always returned: "http.method", "http.scheme", // "http.target", "net.host.name". The following attributes are returned if // they related values are defined in req: "net.host.port", "net.sock.peer.addr", // "net.sock.peer.port", "user_agent.original", "http.client_ip". func HTTPServerRequest(server string, req *http.Request) []attribute.KeyValue { return hc.ServerRequest(server, req) } // HTTPServerRequestMetrics returns metric attributes for an HTTP request received by a // server. // // The server must be the primary server name if it is known. For example this // would be the ServerName directive // (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache // server, and the server_name directive // (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an // nginx server. More generically, the primary server name would be the host // header value that matches the default virtual host of an HTTP server. It // should include the host identifier and if a port is used to route to the // server that port identifier should be included as an appropriate port // suffix. // // If the primary server name is not known, server should be an empty string. // The req Host will be used to determine the server instead. // // The following attributes are always returned: "http.method", "http.scheme", // "net.host.name". The following attributes are returned if they related // values are defined in req: "net.host.port". func HTTPServerRequestMetrics(server string, req *http.Request) []attribute.KeyValue { return hc.ServerRequestMetrics(server, req) } // HTTPServerStatus returns a span status code and message for an HTTP status code // value returned by a server. Status codes in the 400-499 range are not // returned as errors. func HTTPServerStatus(code int) (codes.Code, string) { return hc.ServerStatus(code) } // httpConv are the HTTP semantic convention attributes defined for a version // of the OpenTelemetry specification. type httpConv struct { NetConv *netConv HTTPClientIPKey attribute.Key HTTPMethodKey attribute.Key HTTPRequestContentLengthKey attribute.Key HTTPResponseContentLengthKey attribute.Key HTTPRouteKey attribute.Key HTTPSchemeHTTP attribute.KeyValue HTTPSchemeHTTPS attribute.KeyValue HTTPStatusCodeKey attribute.Key HTTPTargetKey attribute.Key HTTPURLKey attribute.Key UserAgentOriginalKey attribute.Key } var hc = &httpConv{ NetConv: nc, HTTPClientIPKey: semconv.HTTPClientIPKey, HTTPMethodKey: semconv.HTTPMethodKey, HTTPRequestContentLengthKey: semconv.HTTPRequestContentLengthKey, HTTPResponseContentLengthKey: semconv.HTTPResponseContentLengthKey, HTTPRouteKey: semconv.HTTPRouteKey, HTTPSchemeHTTP: semconv.HTTPSchemeHTTP, HTTPSchemeHTTPS: semconv.HTTPSchemeHTTPS, HTTPStatusCodeKey: semconv.HTTPStatusCodeKey, HTTPTargetKey: semconv.HTTPTargetKey, HTTPURLKey: semconv.HTTPURLKey, UserAgentOriginalKey: semconv.UserAgentOriginalKey, } // ClientResponse returns attributes for an HTTP response received by a client // from a server. The following attributes are returned if the related values // are defined in resp: "http.status.code", "http.response_content_length". // // This does not add all OpenTelemetry required attributes for an HTTP event, // it assumes ClientRequest was used to create the span with a complete set of // attributes. If a complete set of attributes can be generated using the // request contained in resp. For example: // // append(ClientResponse(resp), ClientRequest(resp.Request)...) func (c *httpConv) ClientResponse(resp *http.Response) []attribute.KeyValue { /* The following semantic conventions are returned if present: http.status_code int http.response_content_length int */ var n int if resp.StatusCode > 0 { n++ } if resp.ContentLength > 0 { n++ } attrs := make([]attribute.KeyValue, 0, n) if resp.StatusCode > 0 { attrs = append(attrs, c.HTTPStatusCodeKey.Int(resp.StatusCode)) } if resp.ContentLength > 0 { attrs = append(attrs, c.HTTPResponseContentLengthKey.Int(int(resp.ContentLength))) } return attrs } // ClientRequest returns attributes for an HTTP request made by a client. The // following attributes are always returned: "http.url", "http.method", // "net.peer.name". The following attributes are returned if the related values // are defined in req: "net.peer.port", "user_agent.original", // "http.request_content_length", "user_agent.original". func (c *httpConv) ClientRequest(req *http.Request) []attribute.KeyValue { /* The following semantic conventions are returned if present: http.method string user_agent.original string http.url string net.peer.name string net.peer.port int http.request_content_length int */ /* The following semantic conventions are not returned: http.status_code This requires the response. See ClientResponse. http.response_content_length This requires the response. See ClientResponse. net.sock.family This requires the socket used. net.sock.peer.addr This requires the socket used. net.sock.peer.name This requires the socket used. net.sock.peer.port This requires the socket used. http.resend_count This is something outside of a single request. net.protocol.name The value is the Request is ignored, and the go client will always use "http". net.protocol.version The value in the Request is ignored, and the go client will always use 1.1 or 2.0. */ n := 3 // URL, peer name, proto, and method. var h string if req.URL != nil { h = req.URL.Host } peer, p := firstHostPort(h, req.Header.Get("Host")) port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", p) if port > 0 { n++ } useragent := req.UserAgent() if useragent != "" { n++ } if req.ContentLength > 0 { n++ } attrs := make([]attribute.KeyValue, 0, n) attrs = append(attrs, c.method(req.Method)) var u string if req.URL != nil { // Remove any username/password info that may be in the URL. userinfo := req.URL.User req.URL.User = nil u = req.URL.String() // Restore any username/password info that was removed. req.URL.User = userinfo } attrs = append(attrs, c.HTTPURLKey.String(u)) attrs = append(attrs, c.NetConv.PeerName(peer)) if port > 0 { attrs = append(attrs, c.NetConv.PeerPort(port)) } if useragent != "" { attrs = append(attrs, c.UserAgentOriginalKey.String(useragent)) } if l := req.ContentLength; l > 0 { attrs = append(attrs, c.HTTPRequestContentLengthKey.Int64(l)) } return attrs } // ClientRequestMetrics returns metric attributes for an HTTP request made by a client. The // following attributes are always returned: "http.method", "net.peer.name". // The following attributes are returned if the related values // are defined in req: "net.peer.port". func (c *httpConv) ClientRequestMetrics(req *http.Request) []attribute.KeyValue { /* The following semantic conventions are returned if present: http.method string net.peer.name string net.peer.port int */ n := 2 // method, peer name. var h string if req.URL != nil { h = req.URL.Host } peer, p := firstHostPort(h, req.Header.Get("Host")) port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", p) if port > 0 { n++ } attrs := make([]attribute.KeyValue, 0, n) attrs = append(attrs, c.method(req.Method), c.NetConv.PeerName(peer)) if port > 0 { attrs = append(attrs, c.NetConv.PeerPort(port)) } return attrs } // ServerRequest returns attributes for an HTTP request received by a server. // // The server must be the primary server name if it is known. For example this // would be the ServerName directive // (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache // server, and the server_name directive // (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an // nginx server. More generically, the primary server name would be the host // header value that matches the default virtual host of an HTTP server. It // should include the host identifier and if a port is used to route to the // server that port identifier should be included as an appropriate port // suffix. // // If the primary server name is not known, server should be an empty string. // The req Host will be used to determine the server instead. // // The following attributes are always returned: "http.method", "http.scheme", // "http.target", "net.host.name". The following attributes are returned if they // related values are defined in req: "net.host.port", "net.sock.peer.addr", // "net.sock.peer.port", "user_agent.original", "http.client_ip", // "net.protocol.name", "net.protocol.version". func (c *httpConv) ServerRequest(server string, req *http.Request) []attribute.KeyValue { /* The following semantic conventions are returned if present: http.method string http.scheme string net.host.name string net.host.port int net.sock.peer.addr string net.sock.peer.port int user_agent.original string http.client_ip string net.protocol.name string Note: not set if the value is "http". net.protocol.version string http.target string Note: doesn't include the query parameter. */ /* The following semantic conventions are not returned: http.status_code This requires the response. http.request_content_length This requires the len() of body, which can mutate it. http.response_content_length This requires the response. http.route This is not available. net.sock.peer.name This would require a DNS lookup. net.sock.host.addr The request doesn't have access to the underlying socket. net.sock.host.port The request doesn't have access to the underlying socket. */ n := 4 // Method, scheme, proto, and host name. var host string var p int if server == "" { host, p = splitHostPort(req.Host) } else { // Prioritize the primary server name. host, p = splitHostPort(server) if p < 0 { _, p = splitHostPort(req.Host) } } hostPort := requiredHTTPPort(req.TLS != nil, p) if hostPort > 0 { n++ } peer, peerPort := splitHostPort(req.RemoteAddr) if peer != "" { n++ if peerPort > 0 { n++ } } useragent := req.UserAgent() if useragent != "" { n++ } clientIP := serverClientIP(req.Header.Get("X-Forwarded-For")) if clientIP != "" { n++ } var target string if req.URL != nil { target = req.URL.Path if target != "" { n++ } } protoName, protoVersion := netProtocol(req.Proto) if protoName != "" && protoName != "http" { n++ } if protoVersion != "" { n++ } attrs := make([]attribute.KeyValue, 0, n) attrs = append(attrs, c.method(req.Method)) attrs = append(attrs, c.scheme(req.TLS != nil)) attrs = append(attrs, c.NetConv.HostName(host)) if hostPort > 0 { attrs = append(attrs, c.NetConv.HostPort(hostPort)) } if peer != "" { // The Go HTTP server sets RemoteAddr to "IP:port", this will not be a // file-path that would be interpreted with a sock family. attrs = append(attrs, c.NetConv.SockPeerAddr(peer)) if peerPort > 0 { attrs = append(attrs, c.NetConv.SockPeerPort(peerPort)) } } if useragent != "" { attrs = append(attrs, c.UserAgentOriginalKey.String(useragent)) } if clientIP != "" { attrs = append(attrs, c.HTTPClientIPKey.String(clientIP)) } if target != "" { attrs = append(attrs, c.HTTPTargetKey.String(target)) } if protoName != "" && protoName != "http" { attrs = append(attrs, c.NetConv.NetProtocolName.String(protoName)) } if protoVersion != "" { attrs = append(attrs, c.NetConv.NetProtocolVersion.String(protoVersion)) } return attrs } // ServerRequestMetrics returns metric attributes for an HTTP request received // by a server. // // The server must be the primary server name if it is known. For example this // would be the ServerName directive // (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache // server, and the server_name directive // (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an // nginx server. More generically, the primary server name would be the host // header value that matches the default virtual host of an HTTP server. It // should include the host identifier and if a port is used to route to the // server that port identifier should be included as an appropriate port // suffix. // // If the primary server name is not known, server should be an empty string. // The req Host will be used to determine the server instead. // // The following attributes are always returned: "http.method", "http.scheme", // "net.host.name". The following attributes are returned if they related // values are defined in req: "net.host.port". func (c *httpConv) ServerRequestMetrics(server string, req *http.Request) []attribute.KeyValue { /* The following semantic conventions are returned if present: http.scheme string http.route string http.method string http.status_code int net.host.name string net.host.port int net.protocol.name string Note: not set if the value is "http". net.protocol.version string */ n := 3 // Method, scheme, and host name. var host string var p int if server == "" { host, p = splitHostPort(req.Host) } else { // Prioritize the primary server name. host, p = splitHostPort(server) if p < 0 { _, p = splitHostPort(req.Host) } } hostPort := requiredHTTPPort(req.TLS != nil, p) if hostPort > 0 { n++ } protoName, protoVersion := netProtocol(req.Proto) if protoName != "" { n++ } if protoVersion != "" { n++ } attrs := make([]attribute.KeyValue, 0, n) attrs = append(attrs, c.methodMetric(req.Method)) attrs = append(attrs, c.scheme(req.TLS != nil)) attrs = append(attrs, c.NetConv.HostName(host)) if hostPort > 0 { attrs = append(attrs, c.NetConv.HostPort(hostPort)) } if protoName != "" { attrs = append(attrs, c.NetConv.NetProtocolName.String(protoName)) } if protoVersion != "" { attrs = append(attrs, c.NetConv.NetProtocolVersion.String(protoVersion)) } return attrs } func (c *httpConv) method(method string) attribute.KeyValue { if method == "" { return c.HTTPMethodKey.String(http.MethodGet) } return c.HTTPMethodKey.String(method) } func (c *httpConv) methodMetric(method string) attribute.KeyValue { method = strings.ToUpper(method) switch method { case http.MethodConnect, http.MethodDelete, http.MethodGet, http.MethodHead, http.MethodOptions, http.MethodPatch, http.MethodPost, http.MethodPut, http.MethodTrace: default: method = "_OTHER" } return c.HTTPMethodKey.String(method) } func (c *httpConv) scheme(https bool) attribute.KeyValue { // nolint:revive if https { return c.HTTPSchemeHTTPS } return c.HTTPSchemeHTTP } func serverClientIP(xForwardedFor string) string { if idx := strings.Index(xForwardedFor, ","); idx >= 0 { xForwardedFor = xForwardedFor[:idx] } return xForwardedFor } func requiredHTTPPort(https bool, port int) int { // nolint:revive if https { if port > 0 && port != 443 { return port } } else { if port > 0 && port != 80 { return port } } return -1 } // Return the request host and port from the first non-empty source. func firstHostPort(source ...string) (host string, port int) { for _, hostport := range source { host, port = splitHostPort(hostport) if host != "" || port > 0 { break } } return } // ClientStatus returns a span status code and message for an HTTP status code // value received by a client. func (c *httpConv) ClientStatus(code int) (codes.Code, string) { if code < 100 || code >= 600 { return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) } if code >= 400 { return codes.Error, "" } return codes.Unset, "" } // ServerStatus returns a span status code and message for an HTTP status code // value returned by a server. Status codes in the 400-499 range are not // returned as errors. func (c *httpConv) ServerStatus(code int) (codes.Code, string) { if code < 100 || code >= 600 { return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) } if code >= 500 { return codes.Error, "" } return codes.Unset, "" } httpconv_test.go000066400000000000000000000372171470323427300404760ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/internal/semconvutil// Code created by gotmpl. DO NOT MODIFY. // source: internal/shared/semconvutil/httpconv_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconvutil import ( "net/http" "net/http/httptest" "net/url" "strconv" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" ) func TestHTTPClientResponse(t *testing.T) { const stat, n = 201, 397 resp := &http.Response{ StatusCode: stat, ContentLength: n, } got := HTTPClientResponse(resp) assert.Equal(t, 2, cap(got), "slice capacity") assert.ElementsMatch(t, []attribute.KeyValue{ attribute.Key("http.status_code").Int(stat), attribute.Key("http.response_content_length").Int(n), }, got) } func TestHTTPSClientRequest(t *testing.T) { req := &http.Request{ Method: http.MethodGet, URL: &url.URL{ Scheme: "https", Host: "127.0.0.1:443", Path: "/resource", }, Proto: "HTTP/1.0", ProtoMajor: 1, ProtoMinor: 0, } assert.ElementsMatch( t, []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.url", "https://127.0.0.1:443/resource"), attribute.String("net.peer.name", "127.0.0.1"), }, HTTPClientRequest(req), ) } func TestHTTPSClientRequestMetrics(t *testing.T) { req := &http.Request{ Method: http.MethodGet, URL: &url.URL{ Scheme: "https", Host: "127.0.0.1:443", Path: "/resource", }, Proto: "HTTP/1.0", ProtoMajor: 1, ProtoMinor: 0, } assert.ElementsMatch( t, []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("net.peer.name", "127.0.0.1"), }, HTTPClientRequestMetrics(req), ) } func TestHTTPClientRequest(t *testing.T) { const ( user = "alice" n = 128 agent = "Go-http-client/1.1" ) req := &http.Request{ Method: http.MethodGet, URL: &url.URL{ Scheme: "http", Host: "127.0.0.1:8080", Path: "/resource", }, Proto: "HTTP/1.0", ProtoMajor: 1, ProtoMinor: 0, Header: http.Header{ "User-Agent": []string{agent}, }, ContentLength: n, } req.SetBasicAuth(user, "pswrd") assert.ElementsMatch( t, []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.url", "http://127.0.0.1:8080/resource"), attribute.String("net.peer.name", "127.0.0.1"), attribute.Int("net.peer.port", 8080), attribute.String("user_agent.original", agent), attribute.Int("http.request_content_length", n), }, HTTPClientRequest(req), ) } func TestHTTPClientRequestMetrics(t *testing.T) { const ( user = "alice" n = 128 agent = "Go-http-client/1.1" ) req := &http.Request{ Method: http.MethodGet, URL: &url.URL{ Scheme: "http", Host: "127.0.0.1:8080", Path: "/resource", }, Proto: "HTTP/1.0", ProtoMajor: 1, ProtoMinor: 0, Header: http.Header{ "User-Agent": []string{agent}, }, ContentLength: n, } req.SetBasicAuth(user, "pswrd") assert.ElementsMatch( t, []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("net.peer.name", "127.0.0.1"), attribute.Int("net.peer.port", 8080), }, HTTPClientRequestMetrics(req), ) } func TestHTTPClientRequestRequired(t *testing.T) { req := new(http.Request) var got []attribute.KeyValue assert.NotPanics(t, func() { got = HTTPClientRequest(req) }) want := []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.url", ""), attribute.String("net.peer.name", ""), } assert.Equal(t, want, got) } func TestHTTPServerRequest(t *testing.T) { got := make(chan *http.Request, 1) handler := func(w http.ResponseWriter, r *http.Request) { got <- r w.WriteHeader(http.StatusOK) } srv := httptest.NewServer(http.HandlerFunc(handler)) defer srv.Close() srvURL, err := url.Parse(srv.URL) require.NoError(t, err) srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) require.NoError(t, err) resp, err := srv.Client().Get(srv.URL) require.NoError(t, err) require.NoError(t, resp.Body.Close()) req := <-got peer, peerPort := splitHostPort(req.RemoteAddr) const user = "alice" req.SetBasicAuth(user, "pswrd") const clientIP = "127.0.0.5" req.Header.Add("X-Forwarded-For", clientIP) assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.scheme", "http"), attribute.String("net.host.name", srvURL.Hostname()), attribute.Int("net.host.port", int(srvPort)), attribute.String("net.sock.peer.addr", peer), attribute.Int("net.sock.peer.port", peerPort), attribute.String("user_agent.original", "Go-http-client/1.1"), attribute.String("http.client_ip", clientIP), attribute.String("net.protocol.version", "1.1"), attribute.String("http.target", "/"), }, HTTPServerRequest("", req)) } func TestHTTPServerRequestMetrics(t *testing.T) { got := make(chan *http.Request, 1) handler := func(w http.ResponseWriter, r *http.Request) { got <- r w.WriteHeader(http.StatusOK) } srv := httptest.NewServer(http.HandlerFunc(handler)) defer srv.Close() srvURL, err := url.Parse(srv.URL) require.NoError(t, err) srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) require.NoError(t, err) resp, err := srv.Client().Get(srv.URL) require.NoError(t, err) require.NoError(t, resp.Body.Close()) req := <-got assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.scheme", "http"), attribute.String("net.host.name", srvURL.Hostname()), attribute.Int("net.host.port", int(srvPort)), attribute.String("net.protocol.name", "http"), attribute.String("net.protocol.version", "1.1"), }, HTTPServerRequestMetrics("", req)) } func TestHTTPServerName(t *testing.T) { req := new(http.Request) var got []attribute.KeyValue const ( host = "test.semconv.server" port = 8080 ) portStr := strconv.Itoa(port) server := host + ":" + portStr assert.NotPanics(t, func() { got = HTTPServerRequest(server, req) }) assert.Contains(t, got, attribute.String("net.host.name", host)) assert.Contains(t, got, attribute.Int("net.host.port", port)) req = &http.Request{Host: "alt.host.name:" + portStr} // The server parameter does not include a port, ServerRequest should use // the port in the request Host field. assert.NotPanics(t, func() { got = HTTPServerRequest(host, req) }) assert.Contains(t, got, attribute.String("net.host.name", host)) assert.Contains(t, got, attribute.Int("net.host.port", port)) } func TestHTTPServerRequestFailsGracefully(t *testing.T) { req := new(http.Request) var got []attribute.KeyValue assert.NotPanics(t, func() { got = HTTPServerRequest("", req) }) want := []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.scheme", "http"), attribute.String("net.host.name", ""), } assert.ElementsMatch(t, want, got) } func TestHTTPMethod(t *testing.T) { assert.Equal(t, attribute.String("http.method", "POST"), hc.method("POST")) assert.Equal(t, attribute.String("http.method", "GET"), hc.method("")) assert.Equal(t, attribute.String("http.method", "garbage"), hc.method("garbage")) } func TestHTTPScheme(t *testing.T) { assert.Equal(t, attribute.String("http.scheme", "http"), hc.scheme(false)) assert.Equal(t, attribute.String("http.scheme", "https"), hc.scheme(true)) } func TestHTTPServerClientIP(t *testing.T) { tests := []struct { xForwardedFor string want string }{ {"", ""}, {"127.0.0.1", "127.0.0.1"}, {"127.0.0.1,127.0.0.5", "127.0.0.1"}, } for _, test := range tests { got := serverClientIP(test.xForwardedFor) assert.Equal(t, test.want, got, test.xForwardedFor) } } func TestRequiredHTTPPort(t *testing.T) { tests := []struct { https bool port int want int }{ {true, 443, -1}, {true, 80, 80}, {true, 8081, 8081}, {false, 443, 443}, {false, 80, -1}, {false, 8080, 8080}, } for _, test := range tests { got := requiredHTTPPort(test.https, test.port) assert.Equal(t, test.want, got, test.https, test.port) } } func TestFirstHostPort(t *testing.T) { host, port := "127.0.0.1", 8080 hostport := "127.0.0.1:8080" sources := [][]string{ {hostport}, {"", hostport}, {"", "", hostport}, {"", "", hostport, ""}, {"", "", hostport, "127.0.0.3:80"}, } for _, src := range sources { h, p := firstHostPort(src...) assert.Equal(t, host, h, src) assert.Equal(t, port, p, src) } } func TestHTTPClientStatus(t *testing.T) { tests := []struct { code int stat codes.Code msg bool }{ {0, codes.Error, true}, {http.StatusContinue, codes.Unset, false}, {http.StatusSwitchingProtocols, codes.Unset, false}, {http.StatusProcessing, codes.Unset, false}, {http.StatusEarlyHints, codes.Unset, false}, {http.StatusOK, codes.Unset, false}, {http.StatusCreated, codes.Unset, false}, {http.StatusAccepted, codes.Unset, false}, {http.StatusNonAuthoritativeInfo, codes.Unset, false}, {http.StatusNoContent, codes.Unset, false}, {http.StatusResetContent, codes.Unset, false}, {http.StatusPartialContent, codes.Unset, false}, {http.StatusMultiStatus, codes.Unset, false}, {http.StatusAlreadyReported, codes.Unset, false}, {http.StatusIMUsed, codes.Unset, false}, {http.StatusMultipleChoices, codes.Unset, false}, {http.StatusMovedPermanently, codes.Unset, false}, {http.StatusFound, codes.Unset, false}, {http.StatusSeeOther, codes.Unset, false}, {http.StatusNotModified, codes.Unset, false}, {http.StatusUseProxy, codes.Unset, false}, {306, codes.Unset, false}, {http.StatusTemporaryRedirect, codes.Unset, false}, {http.StatusPermanentRedirect, codes.Unset, false}, {http.StatusBadRequest, codes.Error, false}, {http.StatusUnauthorized, codes.Error, false}, {http.StatusPaymentRequired, codes.Error, false}, {http.StatusForbidden, codes.Error, false}, {http.StatusNotFound, codes.Error, false}, {http.StatusMethodNotAllowed, codes.Error, false}, {http.StatusNotAcceptable, codes.Error, false}, {http.StatusProxyAuthRequired, codes.Error, false}, {http.StatusRequestTimeout, codes.Error, false}, {http.StatusConflict, codes.Error, false}, {http.StatusGone, codes.Error, false}, {http.StatusLengthRequired, codes.Error, false}, {http.StatusPreconditionFailed, codes.Error, false}, {http.StatusRequestEntityTooLarge, codes.Error, false}, {http.StatusRequestURITooLong, codes.Error, false}, {http.StatusUnsupportedMediaType, codes.Error, false}, {http.StatusRequestedRangeNotSatisfiable, codes.Error, false}, {http.StatusExpectationFailed, codes.Error, false}, {http.StatusTeapot, codes.Error, false}, {http.StatusMisdirectedRequest, codes.Error, false}, {http.StatusUnprocessableEntity, codes.Error, false}, {http.StatusLocked, codes.Error, false}, {http.StatusFailedDependency, codes.Error, false}, {http.StatusTooEarly, codes.Error, false}, {http.StatusUpgradeRequired, codes.Error, false}, {http.StatusPreconditionRequired, codes.Error, false}, {http.StatusTooManyRequests, codes.Error, false}, {http.StatusRequestHeaderFieldsTooLarge, codes.Error, false}, {http.StatusUnavailableForLegalReasons, codes.Error, false}, {499, codes.Error, false}, {http.StatusInternalServerError, codes.Error, false}, {http.StatusNotImplemented, codes.Error, false}, {http.StatusBadGateway, codes.Error, false}, {http.StatusServiceUnavailable, codes.Error, false}, {http.StatusGatewayTimeout, codes.Error, false}, {http.StatusHTTPVersionNotSupported, codes.Error, false}, {http.StatusVariantAlsoNegotiates, codes.Error, false}, {http.StatusInsufficientStorage, codes.Error, false}, {http.StatusLoopDetected, codes.Error, false}, {http.StatusNotExtended, codes.Error, false}, {http.StatusNetworkAuthenticationRequired, codes.Error, false}, {600, codes.Error, true}, } for _, test := range tests { t.Run(strconv.Itoa(test.code), func(t *testing.T) { c, msg := HTTPClientStatus(test.code) assert.Equal(t, test.stat, c) if test.msg && msg == "" { t.Errorf("expected non-empty message for %d", test.code) } else if !test.msg && msg != "" { t.Errorf("expected empty message for %d, got: %s", test.code, msg) } }) } } func TestHTTPServerStatus(t *testing.T) { tests := []struct { code int stat codes.Code msg bool }{ {0, codes.Error, true}, {http.StatusContinue, codes.Unset, false}, {http.StatusSwitchingProtocols, codes.Unset, false}, {http.StatusProcessing, codes.Unset, false}, {http.StatusEarlyHints, codes.Unset, false}, {http.StatusOK, codes.Unset, false}, {http.StatusCreated, codes.Unset, false}, {http.StatusAccepted, codes.Unset, false}, {http.StatusNonAuthoritativeInfo, codes.Unset, false}, {http.StatusNoContent, codes.Unset, false}, {http.StatusResetContent, codes.Unset, false}, {http.StatusPartialContent, codes.Unset, false}, {http.StatusMultiStatus, codes.Unset, false}, {http.StatusAlreadyReported, codes.Unset, false}, {http.StatusIMUsed, codes.Unset, false}, {http.StatusMultipleChoices, codes.Unset, false}, {http.StatusMovedPermanently, codes.Unset, false}, {http.StatusFound, codes.Unset, false}, {http.StatusSeeOther, codes.Unset, false}, {http.StatusNotModified, codes.Unset, false}, {http.StatusUseProxy, codes.Unset, false}, {306, codes.Unset, false}, {http.StatusTemporaryRedirect, codes.Unset, false}, {http.StatusPermanentRedirect, codes.Unset, false}, {http.StatusBadRequest, codes.Unset, false}, {http.StatusUnauthorized, codes.Unset, false}, {http.StatusPaymentRequired, codes.Unset, false}, {http.StatusForbidden, codes.Unset, false}, {http.StatusNotFound, codes.Unset, false}, {http.StatusMethodNotAllowed, codes.Unset, false}, {http.StatusNotAcceptable, codes.Unset, false}, {http.StatusProxyAuthRequired, codes.Unset, false}, {http.StatusRequestTimeout, codes.Unset, false}, {http.StatusConflict, codes.Unset, false}, {http.StatusGone, codes.Unset, false}, {http.StatusLengthRequired, codes.Unset, false}, {http.StatusPreconditionFailed, codes.Unset, false}, {http.StatusRequestEntityTooLarge, codes.Unset, false}, {http.StatusRequestURITooLong, codes.Unset, false}, {http.StatusUnsupportedMediaType, codes.Unset, false}, {http.StatusRequestedRangeNotSatisfiable, codes.Unset, false}, {http.StatusExpectationFailed, codes.Unset, false}, {http.StatusTeapot, codes.Unset, false}, {http.StatusMisdirectedRequest, codes.Unset, false}, {http.StatusUnprocessableEntity, codes.Unset, false}, {http.StatusLocked, codes.Unset, false}, {http.StatusFailedDependency, codes.Unset, false}, {http.StatusTooEarly, codes.Unset, false}, {http.StatusUpgradeRequired, codes.Unset, false}, {http.StatusPreconditionRequired, codes.Unset, false}, {http.StatusTooManyRequests, codes.Unset, false}, {http.StatusRequestHeaderFieldsTooLarge, codes.Unset, false}, {http.StatusUnavailableForLegalReasons, codes.Unset, false}, {499, codes.Unset, false}, {http.StatusInternalServerError, codes.Error, false}, {http.StatusNotImplemented, codes.Error, false}, {http.StatusBadGateway, codes.Error, false}, {http.StatusServiceUnavailable, codes.Error, false}, {http.StatusGatewayTimeout, codes.Error, false}, {http.StatusHTTPVersionNotSupported, codes.Error, false}, {http.StatusVariantAlsoNegotiates, codes.Error, false}, {http.StatusInsufficientStorage, codes.Error, false}, {http.StatusLoopDetected, codes.Error, false}, {http.StatusNotExtended, codes.Error, false}, {http.StatusNetworkAuthenticationRequired, codes.Error, false}, {600, codes.Error, true}, } for _, test := range tests { c, msg := HTTPServerStatus(test.code) assert.Equal(t, test.stat, c) if test.msg && msg == "" { t.Errorf("expected non-empty message for %d", test.code) } else if !test.msg && msg != "" { t.Errorf("expected empty message for %d, got: %s", test.code, msg) } } } netconv.go000066400000000000000000000122471470323427300372420ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/internal/semconvutil// Code created by gotmpl. DO NOT MODIFY. // source: internal/shared/semconvutil/netconv.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil" import ( "net" "strconv" "strings" "go.opentelemetry.io/otel/attribute" semconv "go.opentelemetry.io/otel/semconv/v1.20.0" ) // NetTransport returns a trace attribute describing the transport protocol of the // passed network. See the net.Dial for information about acceptable network // values. func NetTransport(network string) attribute.KeyValue { return nc.Transport(network) } // netConv are the network semantic convention attributes defined for a version // of the OpenTelemetry specification. type netConv struct { NetHostNameKey attribute.Key NetHostPortKey attribute.Key NetPeerNameKey attribute.Key NetPeerPortKey attribute.Key NetProtocolName attribute.Key NetProtocolVersion attribute.Key NetSockFamilyKey attribute.Key NetSockPeerAddrKey attribute.Key NetSockPeerPortKey attribute.Key NetSockHostAddrKey attribute.Key NetSockHostPortKey attribute.Key NetTransportOther attribute.KeyValue NetTransportTCP attribute.KeyValue NetTransportUDP attribute.KeyValue NetTransportInProc attribute.KeyValue } var nc = &netConv{ NetHostNameKey: semconv.NetHostNameKey, NetHostPortKey: semconv.NetHostPortKey, NetPeerNameKey: semconv.NetPeerNameKey, NetPeerPortKey: semconv.NetPeerPortKey, NetProtocolName: semconv.NetProtocolNameKey, NetProtocolVersion: semconv.NetProtocolVersionKey, NetSockFamilyKey: semconv.NetSockFamilyKey, NetSockPeerAddrKey: semconv.NetSockPeerAddrKey, NetSockPeerPortKey: semconv.NetSockPeerPortKey, NetSockHostAddrKey: semconv.NetSockHostAddrKey, NetSockHostPortKey: semconv.NetSockHostPortKey, NetTransportOther: semconv.NetTransportOther, NetTransportTCP: semconv.NetTransportTCP, NetTransportUDP: semconv.NetTransportUDP, NetTransportInProc: semconv.NetTransportInProc, } func (c *netConv) Transport(network string) attribute.KeyValue { switch network { case "tcp", "tcp4", "tcp6": return c.NetTransportTCP case "udp", "udp4", "udp6": return c.NetTransportUDP case "unix", "unixgram", "unixpacket": return c.NetTransportInProc default: // "ip:*", "ip4:*", and "ip6:*" all are considered other. return c.NetTransportOther } } // Host returns attributes for a network host address. func (c *netConv) Host(address string) []attribute.KeyValue { h, p := splitHostPort(address) var n int if h != "" { n++ if p > 0 { n++ } } if n == 0 { return nil } attrs := make([]attribute.KeyValue, 0, n) attrs = append(attrs, c.HostName(h)) if p > 0 { attrs = append(attrs, c.HostPort(p)) } return attrs } func (c *netConv) HostName(name string) attribute.KeyValue { return c.NetHostNameKey.String(name) } func (c *netConv) HostPort(port int) attribute.KeyValue { return c.NetHostPortKey.Int(port) } func family(network, address string) string { switch network { case "unix", "unixgram", "unixpacket": return "unix" default: if ip := net.ParseIP(address); ip != nil { if ip.To4() == nil { return "inet6" } return "inet" } } return "" } // Peer returns attributes for a network peer address. func (c *netConv) Peer(address string) []attribute.KeyValue { h, p := splitHostPort(address) var n int if h != "" { n++ if p > 0 { n++ } } if n == 0 { return nil } attrs := make([]attribute.KeyValue, 0, n) attrs = append(attrs, c.PeerName(h)) if p > 0 { attrs = append(attrs, c.PeerPort(p)) } return attrs } func (c *netConv) PeerName(name string) attribute.KeyValue { return c.NetPeerNameKey.String(name) } func (c *netConv) PeerPort(port int) attribute.KeyValue { return c.NetPeerPortKey.Int(port) } func (c *netConv) SockPeerAddr(addr string) attribute.KeyValue { return c.NetSockPeerAddrKey.String(addr) } func (c *netConv) SockPeerPort(port int) attribute.KeyValue { return c.NetSockPeerPortKey.Int(port) } // splitHostPort splits a network address hostport of the form "host", // "host%zone", "[host]", "[host%zone], "host:port", "host%zone:port", // "[host]:port", "[host%zone]:port", or ":port" into host or host%zone and // port. // // An empty host is returned if it is not provided or unparsable. A negative // port is returned if it is not provided or unparsable. func splitHostPort(hostport string) (host string, port int) { port = -1 if strings.HasPrefix(hostport, "[") { addrEnd := strings.LastIndex(hostport, "]") if addrEnd < 0 { // Invalid hostport. return } if i := strings.LastIndex(hostport[addrEnd:], ":"); i < 0 { host = hostport[1:addrEnd] return } } else { if i := strings.LastIndex(hostport, ":"); i < 0 { host = hostport return } } host, pStr, err := net.SplitHostPort(hostport) if err != nil { return } p, err := strconv.ParseUint(pStr, 10, 16) if err != nil { return } return host, int(p) // nolint: gosec // Bitsize checked to be 16 above. } func netProtocol(proto string) (name string, version string) { name, version, _ = strings.Cut(proto, "/") name = strings.ToLower(name) return name, version } netconv_test.go000066400000000000000000000130111470323427300402670ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/internal/semconvutil// Code created by gotmpl. DO NOT MODIFY. // source: internal/shared/semconvutil/netconv_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconvutil import ( "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/attribute" ) const ( addr = "127.0.0.1" port = 1834 ) func TestNetTransport(t *testing.T) { transports := map[string]attribute.KeyValue{ "tcp": attribute.String("net.transport", "ip_tcp"), "tcp4": attribute.String("net.transport", "ip_tcp"), "tcp6": attribute.String("net.transport", "ip_tcp"), "udp": attribute.String("net.transport", "ip_udp"), "udp4": attribute.String("net.transport", "ip_udp"), "udp6": attribute.String("net.transport", "ip_udp"), "unix": attribute.String("net.transport", "inproc"), "unixgram": attribute.String("net.transport", "inproc"), "unixpacket": attribute.String("net.transport", "inproc"), "ip:1": attribute.String("net.transport", "other"), "ip:icmp": attribute.String("net.transport", "other"), "ip4:proto": attribute.String("net.transport", "other"), "ip6:proto": attribute.String("net.transport", "other"), } for network, want := range transports { assert.Equal(t, want, NetTransport(network)) } } func TestNetHost(t *testing.T) { testAddrs(t, []addrTest{ {address: "", expected: nil}, {address: "192.0.0.1", expected: []attribute.KeyValue{ nc.HostName("192.0.0.1"), }}, {address: "192.0.0.1:9090", expected: []attribute.KeyValue{ nc.HostName("192.0.0.1"), nc.HostPort(9090), }}, }, nc.Host) } func TestNetHostName(t *testing.T) { expected := attribute.Key("net.host.name").String(addr) assert.Equal(t, expected, nc.HostName(addr)) } func TestNetHostPort(t *testing.T) { expected := attribute.Key("net.host.port").Int(port) assert.Equal(t, expected, nc.HostPort(port)) } func TestNetPeer(t *testing.T) { testAddrs(t, []addrTest{ {address: "", expected: nil}, {address: "example.com", expected: []attribute.KeyValue{ nc.PeerName("example.com"), }}, {address: "/tmp/file", expected: []attribute.KeyValue{ nc.PeerName("/tmp/file"), }}, {address: "192.0.0.1", expected: []attribute.KeyValue{ nc.PeerName("192.0.0.1"), }}, {address: ":9090", expected: nil}, {address: "192.0.0.1:9090", expected: []attribute.KeyValue{ nc.PeerName("192.0.0.1"), nc.PeerPort(9090), }}, }, nc.Peer) } func TestNetPeerName(t *testing.T) { expected := attribute.Key("net.peer.name").String(addr) assert.Equal(t, expected, nc.PeerName(addr)) } func TestNetPeerPort(t *testing.T) { expected := attribute.Key("net.peer.port").Int(port) assert.Equal(t, expected, nc.PeerPort(port)) } func TestNetSockPeerName(t *testing.T) { expected := attribute.Key("net.sock.peer.addr").String(addr) assert.Equal(t, expected, nc.SockPeerAddr(addr)) } func TestNetSockPeerPort(t *testing.T) { expected := attribute.Key("net.sock.peer.port").Int(port) assert.Equal(t, expected, nc.SockPeerPort(port)) } func TestNetFamily(t *testing.T) { tests := []struct { network string address string expect string }{ {"", "", ""}, {"unix", "", "unix"}, {"unix", "gibberish", "unix"}, {"unixgram", "", "unix"}, {"unixgram", "gibberish", "unix"}, {"unixpacket", "gibberish", "unix"}, {"tcp", "123.0.2.8", "inet"}, {"tcp", "gibberish", ""}, {"", "123.0.2.8", "inet"}, {"", "gibberish", ""}, {"tcp", "fe80::1", "inet6"}, {"", "fe80::1", "inet6"}, } for _, test := range tests { got := family(test.network, test.address) assert.Equal(t, test.expect, got, test.network+"/"+test.address) } } func TestSplitHostPort(t *testing.T) { tests := []struct { hostport string host string port int }{ {"", "", -1}, {":8080", "", 8080}, {"127.0.0.1", "127.0.0.1", -1}, {"www.example.com", "www.example.com", -1}, {"127.0.0.1%25en0", "127.0.0.1%25en0", -1}, {"[]", "", -1}, // Ensure this doesn't panic. {"[fe80::1", "", -1}, {"[fe80::1]", "fe80::1", -1}, {"[fe80::1%25en0]", "fe80::1%25en0", -1}, {"[fe80::1]:8080", "fe80::1", 8080}, {"[fe80::1]::", "", -1}, // Too many colons. {"127.0.0.1:", "127.0.0.1", -1}, {"127.0.0.1:port", "127.0.0.1", -1}, {"127.0.0.1:8080", "127.0.0.1", 8080}, {"www.example.com:8080", "www.example.com", 8080}, {"127.0.0.1%25en0:8080", "127.0.0.1%25en0", 8080}, } for _, test := range tests { h, p := splitHostPort(test.hostport) assert.Equal(t, test.host, h, test.hostport) assert.Equal(t, test.port, p, test.hostport) } } type addrTest struct { address string expected []attribute.KeyValue } func testAddrs(t *testing.T, tests []addrTest, f func(string) []attribute.KeyValue) { t.Helper() for _, test := range tests { got := f(test.address) assert.Equal(t, cap(test.expected), cap(got), "slice capacity") assert.ElementsMatch(t, test.expected, got, test.address) } } func TestNetProtocol(t *testing.T) { type testCase struct { name, version string } tests := map[string]testCase{ "HTTP/1.0": {name: "http", version: "1.0"}, "HTTP/1.1": {name: "http", version: "1.1"}, "HTTP/2": {name: "http", version: "2"}, "HTTP/3": {name: "http", version: "3"}, "SPDY": {name: "spdy"}, "SPDY/2": {name: "spdy", version: "2"}, "QUIC": {name: "quic"}, "unknown/proto/2": {name: "unknown", version: "proto/2"}, "other": {name: "other"}, } for proto, want := range tests { name, version := netProtocol(proto) assert.Equal(t, want.name, name) assert.Equal(t, want.version, version) } } open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/labeler.go000066400000000000000000000035201470323427300330610ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" import ( "context" "sync" "go.opentelemetry.io/otel/attribute" ) // Labeler is used to allow instrumented HTTP handlers to add custom attributes to // the metrics recorded by the net/http instrumentation. type Labeler struct { mu sync.Mutex attributes []attribute.KeyValue } // Add attributes to a Labeler. func (l *Labeler) Add(ls ...attribute.KeyValue) { l.mu.Lock() defer l.mu.Unlock() l.attributes = append(l.attributes, ls...) } // Get returns a copy of the attributes added to the Labeler. func (l *Labeler) Get() []attribute.KeyValue { l.mu.Lock() defer l.mu.Unlock() ret := make([]attribute.KeyValue, len(l.attributes)) copy(ret, l.attributes) return ret } type labelerContextKeyType int const lablelerContextKey labelerContextKeyType = 0 // ContextWithLabeler returns a new context with the provided Labeler instance. // Attributes added to the specified labeler will be injected into metrics // emitted by the instrumentation. Only one labeller can be injected into the // context. Injecting it multiple times will override the previous calls. func ContextWithLabeler(parent context.Context, l *Labeler) context.Context { return context.WithValue(parent, lablelerContextKey, l) } // LabelerFromContext retrieves a Labeler instance from the provided context if // one is available. If no Labeler was found in the provided context a new, empty // Labeler is returned and the second return value is false. In this case it is // safe to use the Labeler but any attributes added to it will not be used. func LabelerFromContext(ctx context.Context) (*Labeler, bool) { l, ok := ctx.Value(lablelerContextKey).(*Labeler) if !ok { l = &Labeler{} } return l, ok } open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/test/000077500000000000000000000000001470323427300321035ustar00rootroot00000000000000client_test.go000066400000000000000000000050401470323427300346670ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/test// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package test import ( "context" "net/http" "net/http/httptest" "net/url" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/sdk/trace/tracetest" ) func TestConvenienceWrappers(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := trace.NewTracerProvider(trace.WithSpanProcessor(sr)) orig := otelhttp.DefaultClient otelhttp.DefaultClient = &http.Client{ Transport: otelhttp.NewTransport( http.DefaultTransport, otelhttp.WithTracerProvider(provider), ), } defer func() { otelhttp.DefaultClient = orig }() content := []byte("Hello, world!") ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if _, err := w.Write(content); err != nil { t.Fatal(err) } })) defer ts.Close() ctx := context.Background() res, err := otelhttp.Get(ctx, ts.URL) if err != nil { t.Fatal(err) } res.Body.Close() res, err = otelhttp.Head(ctx, ts.URL) if err != nil { t.Fatal(err) } res.Body.Close() res, err = otelhttp.Post(ctx, ts.URL, "text/plain", strings.NewReader("test")) if err != nil { t.Fatal(err) } res.Body.Close() form := make(url.Values) form.Set("foo", "bar") res, err = otelhttp.PostForm(ctx, ts.URL, form) if err != nil { t.Fatal(err) } res.Body.Close() spans := sr.Ended() require.Len(t, spans, 4) assert.Equal(t, "HTTP GET", spans[0].Name()) assert.Equal(t, "HTTP HEAD", spans[1].Name()) assert.Equal(t, "HTTP POST", spans[2].Name()) assert.Equal(t, "HTTP POST", spans[3].Name()) } func TestClientWithTraceContext(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := trace.NewTracerProvider(trace.WithSpanProcessor(sr)) tracer := provider.Tracer("") ctx, span := tracer.Start(context.Background(), "http requests") content := []byte("Hello, world!") ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if _, err := w.Write(content); err != nil { t.Fatal(err) } })) defer ts.Close() res, err := otelhttp.Get(ctx, ts.URL) if err != nil { t.Fatal(err) } res.Body.Close() span.End() spans := sr.Ended() require.Len(t, spans, 2) assert.Equal(t, "HTTP GET", spans[0].Name()) assert.Equal(t, "http requests", spans[1].Name()) assert.NotEmpty(t, spans[0].Parent().SpanID()) assert.Equal(t, spans[1].SpanContext().SpanID(), spans[0].Parent().SpanID()) } config_test.go000066400000000000000000000067531470323427300346720ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/test// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package test import ( "io" "net/http" "net/http/httptest" "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/sdk/trace/tracetest" ) func TestBasicFilter(t *testing.T) { rr := httptest.NewRecorder() spanRecorder := tracetest.NewSpanRecorder() provider := trace.NewTracerProvider(trace.WithSpanProcessor(spanRecorder)) h := otelhttp.NewHandler( http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if _, err := io.WriteString(w, "hello world"); err != nil { t.Fatal(err) } }), "test_handler", otelhttp.WithTracerProvider(provider), otelhttp.WithFilter(func(r *http.Request) bool { return false }), ) r, err := http.NewRequest(http.MethodGet, "http://localhost/", nil) if err != nil { t.Fatal(err) } h.ServeHTTP(rr, r) if got, expected := rr.Result().StatusCode, http.StatusOK; got != expected { //nolint:bodyclose // False positive for httptest.ResponseRecorder: https://github.com/timakin/bodyclose/issues/59. t.Fatalf("got %d, expected %d", got, expected) } if got := rr.Header().Get("Traceparent"); got != "" { t.Fatal("expected empty trace header") } if got, expected := len(spanRecorder.Ended()), 0; got != expected { t.Fatalf("got %d recorded spans, expected %d", got, expected) } d, err := io.ReadAll(rr.Result().Body) //nolint:bodyclose // False positive for httptest.ResponseRecorder: https://github.com/timakin/bodyclose/issues/59. if err != nil { t.Fatal(err) } if got, expected := string(d), "hello world"; got != expected { t.Fatalf("got %q, expected %q", got, expected) } } func TestSpanNameFormatter(t *testing.T) { testCases := []struct { name string formatter func(s string, r *http.Request) string operation string expected string }{ { name: "default handler formatter", formatter: func(operation string, _ *http.Request) string { return operation }, operation: "test_operation", expected: "test_operation", }, { name: "default transport formatter", formatter: func(_ string, r *http.Request) string { return "HTTP " + r.Method }, expected: "HTTP GET", }, { name: "custom formatter", formatter: func(s string, r *http.Request) string { return r.URL.Path }, operation: "", expected: "/hello", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { rr := httptest.NewRecorder() spanRecorder := tracetest.NewSpanRecorder() provider := trace.NewTracerProvider(trace.WithSpanProcessor(spanRecorder)) handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if _, err := io.WriteString(w, "hello world"); err != nil { t.Fatal(err) } }) h := otelhttp.NewHandler( handler, tc.operation, otelhttp.WithTracerProvider(provider), otelhttp.WithSpanNameFormatter(tc.formatter), ) r, err := http.NewRequest(http.MethodGet, "http://localhost/hello", nil) if err != nil { t.Fatal(err) } h.ServeHTTP(rr, r) if got, expected := rr.Result().StatusCode, http.StatusOK; got != expected { //nolint:bodyclose // False positive for httptest.ResponseRecorder: https://github.com/timakin/bodyclose/issues/59. t.Fatalf("got %d, expected %d", got, expected) } spans := spanRecorder.Ended() if assert.Len(t, spans, 1) { assert.Equal(t, tc.expected, spans[0].Name()) } }) } } open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/test/doc.go000066400000000000000000000006521470323427300332020ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 /* Package test validates the otelhttp instrumentation with the default SDK. This package is in a separate module from the instrumentation it tests to isolate the dependency of the default SDK and not impose this as a transitive dependency for users. */ package test // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/test" open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/test/go.mod000066400000000000000000000015301470323427300332100ustar00rootroot00000000000000module go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/test go 1.22 require ( github.com/stretchr/testify v1.9.0 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/sdk v1.31.0 go.opentelemetry.io/otel/sdk/metric v1.31.0 go.opentelemetry.io/otel/trace v1.31.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect golang.org/x/sys v0.26.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) replace go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp => ../ open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/test/go.sum000066400000000000000000000054501470323427300332420ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc= go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= handler_test.go000066400000000000000000000466611470323427300350440ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/test// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package test import ( "context" "fmt" "io" "net/http" "net/http/httptest" "strconv" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/sdk/instrumentation" sdkmetric "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/sdk/trace/tracetest" semconv "go.opentelemetry.io/otel/semconv/v1.20.0" "go.opentelemetry.io/otel/trace" ) func assertScopeMetrics(t *testing.T, sm metricdata.ScopeMetrics, attrs attribute.Set) { assert.Equal(t, instrumentation.Scope{ Name: "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp", Version: otelhttp.Version(), }, sm.Scope) require.Len(t, sm.Metrics, 3) want := metricdata.Metrics{ Name: "http.server.request.size", Description: "Measures the size of HTTP request messages.", Unit: "By", Data: metricdata.Sum[int64]{ DataPoints: []metricdata.DataPoint[int64]{{Attributes: attrs, Value: 0}}, Temporality: metricdata.CumulativeTemporality, IsMonotonic: true, }, } metricdatatest.AssertEqual(t, want, sm.Metrics[0], metricdatatest.IgnoreTimestamp()) want = metricdata.Metrics{ Name: "http.server.response.size", Description: "Measures the size of HTTP response messages.", Unit: "By", Data: metricdata.Sum[int64]{ DataPoints: []metricdata.DataPoint[int64]{{Attributes: attrs, Value: 11}}, Temporality: metricdata.CumulativeTemporality, IsMonotonic: true, }, } metricdatatest.AssertEqual(t, want, sm.Metrics[1], metricdatatest.IgnoreTimestamp()) want = metricdata.Metrics{ Name: "http.server.duration", Description: "Measures the duration of inbound HTTP requests.", Unit: "ms", Data: metricdata.Histogram[float64]{ DataPoints: []metricdata.HistogramDataPoint[float64]{{Attributes: attrs}}, Temporality: metricdata.CumulativeTemporality, }, } metricdatatest.AssertEqual(t, want, sm.Metrics[2], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue()) } func TestHandlerBasics(t *testing.T) { t.Setenv("OTEL_METRICS_EXEMPLAR_FILTER", "always_off") rr := httptest.NewRecorder() spanRecorder := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(spanRecorder)) reader := sdkmetric.NewManualReader() meterProvider := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) h := otelhttp.NewHandler( http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { l, _ := otelhttp.LabelerFromContext(r.Context()) l.Add(attribute.String("test", "attribute")) if _, err := io.WriteString(w, "hello world"); err != nil { t.Fatal(err) } }), "test_handler", otelhttp.WithTracerProvider(provider), otelhttp.WithMeterProvider(meterProvider), otelhttp.WithPropagators(propagation.TraceContext{}), ) r, err := http.NewRequest(http.MethodGet, "http://localhost/", strings.NewReader("foo")) if err != nil { t.Fatal(err) } h.ServeHTTP(rr, r) rm := metricdata.ResourceMetrics{} err = reader.Collect(context.Background(), &rm) require.NoError(t, err) require.Len(t, rm.ScopeMetrics, 1) attrs := attribute.NewSet( semconv.NetHostName(r.Host), semconv.HTTPSchemeHTTP, semconv.NetProtocolName("http"), semconv.NetProtocolVersion(fmt.Sprintf("1.%d", r.ProtoMinor)), semconv.HTTPMethod("GET"), attribute.String("test", "attribute"), semconv.HTTPStatusCode(200), ) assertScopeMetrics(t, rm.ScopeMetrics[0], attrs) if got, expected := rr.Result().StatusCode, http.StatusOK; got != expected { //nolint:bodyclose // False positive for httptest.ResponseRecorder: https://github.com/timakin/bodyclose/issues/59. t.Fatalf("got %d, expected %d", got, expected) } spans := spanRecorder.Ended() if got, expected := len(spans), 1; got != expected { t.Fatalf("got %d spans, expected %d", got, expected) } if !spans[0].SpanContext().IsValid() { t.Fatalf("invalid span created: %#v", spans[0].SpanContext()) } d, err := io.ReadAll(rr.Result().Body) //nolint:bodyclose // False positive for httptest.ResponseRecorder: https://github.com/timakin/bodyclose/issues/59. if err != nil { t.Fatal(err) } if got, expected := string(d), "hello world"; got != expected { t.Fatalf("got %q, expected %q", got, expected) } } func TestHandlerEmittedAttributes(t *testing.T) { testCases := []struct { name string handler func(http.ResponseWriter, *http.Request) attributes []attribute.KeyValue }{ { name: "With a success handler", handler: func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }, attributes: []attribute.KeyValue{ attribute.Int("http.status_code", http.StatusOK), }, }, { name: "With a failing handler", handler: func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusBadRequest) }, attributes: []attribute.KeyValue{ attribute.Int("http.status_code", http.StatusBadRequest), }, }, { name: "With an empty handler", handler: func(w http.ResponseWriter, r *http.Request) { }, attributes: []attribute.KeyValue{ attribute.Int("http.status_code", http.StatusOK), }, }, { name: "With persisting initial failing status in handler with multiple WriteHeader calls", handler: func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusOK) }, attributes: []attribute.KeyValue{ attribute.Int("http.status_code", http.StatusInternalServerError), }, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider() provider.RegisterSpanProcessor(sr) h := otelhttp.NewHandler( http.HandlerFunc(tc.handler), "test_handler", otelhttp.WithTracerProvider(provider), ) h.ServeHTTP(httptest.NewRecorder(), httptest.NewRequest("GET", "/", nil)) require.Len(t, sr.Ended(), 1, "should emit a span") attrs := sr.Ended()[0].Attributes() for _, a := range tc.attributes { assert.Contains(t, attrs, a) } }) } } type respWriteHeaderCounter struct { http.ResponseWriter headersWritten []int } func (rw *respWriteHeaderCounter) WriteHeader(statusCode int) { rw.headersWritten = append(rw.headersWritten, statusCode) rw.ResponseWriter.WriteHeader(statusCode) } func (rw *respWriteHeaderCounter) Flush() { if f, ok := rw.ResponseWriter.(http.Flusher); ok { f.Flush() } } func TestHandlerPropagateWriteHeaderCalls(t *testing.T) { testCases := []struct { name string handler func(http.ResponseWriter, *http.Request) expectHeadersWritten []int }{ { name: "With a success handler", handler: func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }, expectHeadersWritten: []int{http.StatusOK}, }, { name: "With a failing handler", handler: func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusBadRequest) }, expectHeadersWritten: []int{http.StatusBadRequest}, }, { name: "With an empty handler", handler: func(w http.ResponseWriter, r *http.Request) { }, expectHeadersWritten: nil, }, { name: "With calling WriteHeader twice", handler: func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusOK) }, expectHeadersWritten: []int{http.StatusInternalServerError, http.StatusOK}, }, { name: "When writing the header indirectly through body write", handler: func(w http.ResponseWriter, r *http.Request) { _, _ = w.Write([]byte("hello")) }, expectHeadersWritten: []int{http.StatusOK}, }, { name: "With a header already written when writing the body", handler: func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusBadRequest) _, _ = w.Write([]byte("hello")) }, expectHeadersWritten: []int{http.StatusBadRequest}, }, { name: "When writing the header indirectly through flush", handler: func(w http.ResponseWriter, r *http.Request) { f := w.(http.Flusher) f.Flush() }, expectHeadersWritten: []int{http.StatusOK}, }, { name: "With a header already written when flushing", handler: func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusBadRequest) f := w.(http.Flusher) f.Flush() }, expectHeadersWritten: []int{http.StatusBadRequest}, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider() provider.RegisterSpanProcessor(sr) h := otelhttp.NewHandler( http.HandlerFunc(tc.handler), "test_handler", otelhttp.WithTracerProvider(provider), ) recorder := httptest.NewRecorder() rw := &respWriteHeaderCounter{ResponseWriter: recorder} h.ServeHTTP(rw, httptest.NewRequest("GET", "/", nil)) require.EqualValues(t, tc.expectHeadersWritten, rw.headersWritten, "should propagate all WriteHeader calls to underlying ResponseWriter") }) } } func TestHandlerRequestWithTraceContext(t *testing.T) { rr := httptest.NewRecorder() h := otelhttp.NewHandler( http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { _, err := w.Write([]byte("hello world")) assert.NoError(t, err) }), "test_handler") r, err := http.NewRequest(http.MethodGet, "http://localhost/", nil) require.NoError(t, err) spanRecorder := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider( sdktrace.WithSpanProcessor(spanRecorder), ) tracer := provider.Tracer("") ctx, span := tracer.Start(context.Background(), "test_request") r = r.WithContext(ctx) h.ServeHTTP(rr, r) assert.Equal(t, http.StatusOK, rr.Result().StatusCode) //nolint:bodyclose // False positive for httptest.ResponseRecorder: https://github.com/timakin/bodyclose/issues/59. span.End() spans := spanRecorder.Ended() require.Len(t, spans, 2) assert.Equal(t, "test_handler", spans[0].Name()) assert.Equal(t, "test_request", spans[1].Name()) assert.NotEmpty(t, spans[0].Parent().SpanID()) assert.Equal(t, spans[1].SpanContext().SpanID(), spans[0].Parent().SpanID()) } func TestWithPublicEndpoint(t *testing.T) { spanRecorder := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider( sdktrace.WithSpanProcessor(spanRecorder), ) remoteSpan := trace.SpanContextConfig{ TraceID: trace.TraceID{0x01}, SpanID: trace.SpanID{0x01}, Remote: true, } prop := propagation.TraceContext{} h := otelhttp.NewHandler( http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { s := trace.SpanFromContext(r.Context()) sc := s.SpanContext() // Should be with new root trace. assert.True(t, sc.IsValid()) assert.False(t, sc.IsRemote()) assert.NotEqual(t, remoteSpan.TraceID, sc.TraceID()) }), "test_handler", otelhttp.WithPublicEndpoint(), otelhttp.WithPropagators(prop), otelhttp.WithTracerProvider(provider), ) r, err := http.NewRequest(http.MethodGet, "http://localhost/", nil) require.NoError(t, err) sc := trace.NewSpanContext(remoteSpan) ctx := trace.ContextWithSpanContext(context.Background(), sc) prop.Inject(ctx, propagation.HeaderCarrier(r.Header)) rr := httptest.NewRecorder() h.ServeHTTP(rr, r) assert.Equal(t, http.StatusOK, rr.Result().StatusCode) //nolint:bodyclose // False positive for httptest.ResponseRecorder: https://github.com/timakin/bodyclose/issues/59. // Recorded span should be linked with an incoming span context. assert.NoError(t, spanRecorder.ForceFlush(ctx)) done := spanRecorder.Ended() require.Len(t, done, 1) require.Len(t, done[0].Links(), 1, "should contain link") require.True(t, sc.Equal(done[0].Links()[0].SpanContext), "should link incoming span context") } func TestWithPublicEndpointFn(t *testing.T) { remoteSpan := trace.SpanContextConfig{ TraceID: trace.TraceID{0x01}, SpanID: trace.SpanID{0x01}, TraceFlags: trace.FlagsSampled, Remote: true, } prop := propagation.TraceContext{} for _, tt := range []struct { name string fn func(*http.Request) bool handlerAssert func(*testing.T, trace.SpanContext) spansAssert func(*testing.T, trace.SpanContext, []sdktrace.ReadOnlySpan) }{ { name: "with the method returning true", fn: func(r *http.Request) bool { return true }, handlerAssert: func(t *testing.T, sc trace.SpanContext) { // Should be with new root trace. assert.True(t, sc.IsValid()) assert.False(t, sc.IsRemote()) assert.NotEqual(t, remoteSpan.TraceID, sc.TraceID()) }, spansAssert: func(t *testing.T, sc trace.SpanContext, spans []sdktrace.ReadOnlySpan) { require.Len(t, spans, 1) require.Len(t, spans[0].Links(), 1, "should contain link") require.True(t, sc.Equal(spans[0].Links()[0].SpanContext), "should link incoming span context") }, }, { name: "with the method returning false", fn: func(r *http.Request) bool { return false }, handlerAssert: func(t *testing.T, sc trace.SpanContext) { // Should have remote span as parent assert.True(t, sc.IsValid()) assert.False(t, sc.IsRemote()) assert.Equal(t, remoteSpan.TraceID, sc.TraceID()) }, spansAssert: func(t *testing.T, _ trace.SpanContext, spans []sdktrace.ReadOnlySpan) { require.Len(t, spans, 1) require.Empty(t, spans[0].Links(), "should not contain link") }, }, } { t.Run(tt.name, func(t *testing.T) { spanRecorder := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider( sdktrace.WithSpanProcessor(spanRecorder), ) h := otelhttp.NewHandler( http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { s := trace.SpanFromContext(r.Context()) tt.handlerAssert(t, s.SpanContext()) }), "test_handler", otelhttp.WithPublicEndpointFn(tt.fn), otelhttp.WithPropagators(prop), otelhttp.WithTracerProvider(provider), ) r, err := http.NewRequest(http.MethodGet, "http://localhost/", nil) require.NoError(t, err) sc := trace.NewSpanContext(remoteSpan) ctx := trace.ContextWithSpanContext(context.Background(), sc) prop.Inject(ctx, propagation.HeaderCarrier(r.Header)) rr := httptest.NewRecorder() h.ServeHTTP(rr, r) assert.Equal(t, http.StatusOK, rr.Result().StatusCode) //nolint:bodyclose // False positive for httptest.ResponseRecorder: https://github.com/timakin/bodyclose/issues/59. // Recorded span should be linked with an incoming span context. assert.NoError(t, spanRecorder.ForceFlush(ctx)) spans := spanRecorder.Ended() tt.spansAssert(t, sc, spans) }) } } func TestSpanStatus(t *testing.T) { testCases := []struct { httpStatusCode int wantSpanStatus codes.Code }{ {http.StatusOK, codes.Unset}, {http.StatusBadRequest, codes.Unset}, {http.StatusInternalServerError, codes.Error}, } for _, tc := range testCases { t.Run(strconv.Itoa(tc.httpStatusCode), func(t *testing.T) { sr := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider() provider.RegisterSpanProcessor(sr) h := otelhttp.NewHandler( http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(tc.httpStatusCode) }), "test_handler", otelhttp.WithTracerProvider(provider), ) h.ServeHTTP(httptest.NewRecorder(), httptest.NewRequest("GET", "/", nil)) require.Len(t, sr.Ended(), 1, "should emit a span") assert.Equal(t, tc.wantSpanStatus, sr.Ended()[0].Status().Code, "should only set Error status for HTTP statuses >= 500") }) } } func TestWithRouteTag(t *testing.T) { t.Setenv("OTEL_METRICS_EXEMPLAR_FILTER", "always_off") route := "/some/route" spanRecorder := tracetest.NewSpanRecorder() tracerProvider := sdktrace.NewTracerProvider() tracerProvider.RegisterSpanProcessor(spanRecorder) metricReader := sdkmetric.NewManualReader() meterProvider := sdkmetric.NewMeterProvider(sdkmetric.WithReader(metricReader)) h := otelhttp.NewHandler( otelhttp.WithRouteTag( route, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusTeapot) }), ), "test_handler", otelhttp.WithTracerProvider(tracerProvider), otelhttp.WithMeterProvider(meterProvider), ) h.ServeHTTP(httptest.NewRecorder(), httptest.NewRequest(http.MethodGet, "/", nil)) want := semconv.HTTPRouteKey.String(route) require.Len(t, spanRecorder.Ended(), 1, "should emit a span") gotSpan := spanRecorder.Ended()[0] require.Contains(t, gotSpan.Attributes(), want, "should add route to span attributes") rm := metricdata.ResourceMetrics{} err := metricReader.Collect(context.Background(), &rm) require.NoError(t, err) require.Len(t, rm.ScopeMetrics, 1, "should emit metrics for one scope") gotMetrics := rm.ScopeMetrics[0].Metrics for _, m := range gotMetrics { switch d := m.Data.(type) { case metricdata.Sum[int64]: require.Len(t, d.DataPoints, 1, "metric '%v' should have exactly one data point", m.Name) require.Contains(t, d.DataPoints[0].Attributes.ToSlice(), want, "should add route to attributes for metric '%v'", m.Name) case metricdata.Sum[float64]: require.Len(t, d.DataPoints, 1, "metric '%v' should have exactly one data point", m.Name) require.Contains(t, d.DataPoints[0].Attributes.ToSlice(), want, "should add route to attributes for metric '%v'", m.Name) case metricdata.Histogram[int64]: require.Len(t, d.DataPoints, 1, "metric '%v' should have exactly one data point", m.Name) require.Contains(t, d.DataPoints[0].Attributes.ToSlice(), want, "should add route to attributes for metric '%v'", m.Name) case metricdata.Histogram[float64]: require.Len(t, d.DataPoints, 1, "metric '%v' should have exactly one data point", m.Name) require.Contains(t, d.DataPoints[0].Attributes.ToSlice(), want, "should add route to attributes for metric '%v'", m.Name) case metricdata.Gauge[int64]: require.Len(t, d.DataPoints, 1, "metric '%v' should have exactly one data point", m.Name) require.Contains(t, d.DataPoints[0].Attributes.ToSlice(), want, "should add route to attributes for metric '%v'", m.Name) case metricdata.Gauge[float64]: require.Len(t, d.DataPoints, 1, "metric '%v' should have exactly one data point", m.Name) require.Contains(t, d.DataPoints[0].Attributes.ToSlice(), want, "should add route to attributes for metric '%v'", m.Name) default: require.Fail(t, "metric has unexpected data type", "metric '%v' has unexpected data type %T", m.Name, m.Data) } } } func BenchmarkHandlerServeHTTP(b *testing.B) { tp := sdktrace.NewTracerProvider() mp := sdkmetric.NewMeterProvider() r, err := http.NewRequest(http.MethodGet, "http://localhost/", nil) require.NoError(b, err) for _, bb := range []struct { name string handler http.Handler }{ { name: "without the otelhttp handler", handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "Hello World") }), }, { name: "with the otelhttp handler", handler: otelhttp.NewHandler( http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "Hello World") }), "test_handler", otelhttp.WithTracerProvider(tp), otelhttp.WithMeterProvider(mp), ), }, } { b.Run(bb.name, func(b *testing.B) { rr := httptest.NewRecorder() b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { bb.handler.ServeHTTP(rr, r) } }) } } transport_test.go000066400000000000000000000434331470323427300354550ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/test// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package test import ( "bytes" "context" "fmt" "io" "net" "net/http" "net/http/httptest" "net/http/httptrace" "runtime" "strconv" "strings" "testing" semconv "go.opentelemetry.io/otel/semconv/v1.20.0" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/instrumentation" sdkmetric "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/sdk/trace/tracetest" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" ) func TestTransportUsesFormatter(t *testing.T) { prop := propagation.TraceContext{} spanRecorder := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(spanRecorder)) content := []byte("Hello, world!") ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := prop.Extract(r.Context(), propagation.HeaderCarrier(r.Header)) span := trace.SpanContextFromContext(ctx) if !span.IsValid() { t.Fatalf("invalid span wrapping handler: %#v", span) } if _, err := w.Write(content); err != nil { t.Fatal(err) } })) defer ts.Close() r, err := http.NewRequest(http.MethodGet, ts.URL, nil) if err != nil { t.Fatal(err) } tr := otelhttp.NewTransport( http.DefaultTransport, otelhttp.WithTracerProvider(provider), otelhttp.WithPropagators(prop), ) c := http.Client{Transport: tr} res, err := c.Do(r) if err != nil { t.Fatal(err) } require.NoError(t, res.Body.Close()) spans := spanRecorder.Ended() spanName := spans[0].Name() expectedName := "HTTP GET" if spanName != expectedName { t.Fatalf("unexpected name: got %s, expected %s", spanName, expectedName) } } func TestTransportErrorStatus(t *testing.T) { // Prepare tracing stuff. spanRecorder := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(spanRecorder)) // Run a server and stop to make sure nothing is listening and force the error. server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) server.Close() // Create our Transport and make request. tr := otelhttp.NewTransport( http.DefaultTransport, otelhttp.WithTracerProvider(provider), ) c := http.Client{Transport: tr} r, err := http.NewRequest(http.MethodGet, server.URL, nil) if err != nil { t.Fatal(err) } resp, err := c.Do(r) if err == nil { if err := resp.Body.Close(); err != nil { t.Errorf("close response body: %v", err) } t.Fatal("transport should have returned an error, it didn't") } // Check span. spans := spanRecorder.Ended() if len(spans) != 1 { t.Fatalf("expected 1 span; got: %d", len(spans)) } span := spans[0] if span.EndTime().IsZero() { t.Errorf("span should be ended; it isn't") } if got := span.Status().Code; got != codes.Error { t.Errorf("expected error status code on span; got: %q", got) } errSubstr := "connect: connection refused" if runtime.GOOS == "windows" { // tls.Dial returns an error that does not contain the substring "connection refused" // on Windows machines // // ref: "dial tcp 127.0.0.1:50115: connectex: No connection could be made because the target machine actively refused it." errSubstr = "No connection could be made because the target machine actively refused it" } if got := span.Status().Description; !strings.Contains(got, errSubstr) { t.Errorf("expected error status message on span; got: %q", got) } } func TestTransportRequestWithTraceContext(t *testing.T) { spanRecorder := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider( sdktrace.WithSpanProcessor(spanRecorder), ) content := []byte("Hello, world!") ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { _, err := w.Write(content) assert.NoError(t, err) })) defer ts.Close() tracer := provider.Tracer("") ctx, span := tracer.Start(context.Background(), "test_span") r, err := http.NewRequest(http.MethodGet, ts.URL, nil) require.NoError(t, err) r = r.WithContext(ctx) tr := otelhttp.NewTransport( http.DefaultTransport, ) c := http.Client{Transport: tr} res, err := c.Do(r) require.NoError(t, err) defer func() { assert.NoError(t, res.Body.Close()) }() span.End() body, err := io.ReadAll(res.Body) require.NoError(t, err) require.Equal(t, content, body) spans := spanRecorder.Ended() require.Len(t, spans, 2) assert.Equal(t, "test_span", spans[0].Name()) assert.Equal(t, "HTTP GET", spans[1].Name()) assert.NotEmpty(t, spans[1].Parent().SpanID()) assert.Equal(t, spans[0].SpanContext().SpanID(), spans[1].Parent().SpanID()) } func TestWithHTTPTrace(t *testing.T) { spanRecorder := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider( sdktrace.WithSpanProcessor(spanRecorder), ) content := []byte("Hello, world!") ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { _, err := w.Write(content) assert.NoError(t, err) })) defer ts.Close() tracer := provider.Tracer("") ctx, span := tracer.Start(context.Background(), "test_span") r, err := http.NewRequest(http.MethodGet, ts.URL, nil) require.NoError(t, err) r = r.WithContext(ctx) clientTracer := func(ctx context.Context) *httptrace.ClientTrace { var span trace.Span return &httptrace.ClientTrace{ GetConn: func(_ string) { _, span = trace.SpanFromContext(ctx).TracerProvider().Tracer("").Start(ctx, "httptrace.GetConn") }, GotConn: func(_ httptrace.GotConnInfo) { if span != nil { span.End() } }, } } tr := otelhttp.NewTransport( http.DefaultTransport, otelhttp.WithClientTrace(clientTracer), ) c := http.Client{Transport: tr} res, err := c.Do(r) require.NoError(t, err) defer func() { assert.NoError(t, res.Body.Close()) }() span.End() body, err := io.ReadAll(res.Body) require.NoError(t, err) require.Equal(t, content, body) spans := spanRecorder.Ended() require.Len(t, spans, 3) assert.Equal(t, "httptrace.GetConn", spans[0].Name()) assert.Equal(t, "test_span", spans[1].Name()) assert.Equal(t, "HTTP GET", spans[2].Name()) assert.NotEmpty(t, spans[0].Parent().SpanID()) assert.NotEmpty(t, spans[2].Parent().SpanID()) assert.Equal(t, spans[2].SpanContext().SpanID(), spans[0].Parent().SpanID()) assert.Equal(t, spans[1].SpanContext().SpanID(), spans[2].Parent().SpanID()) } func TestTransportMetrics(t *testing.T) { requestBody := []byte("john") responseBody := []byte("Hello, world!") t.Run("make http request and read entire response at once", func(t *testing.T) { reader := sdkmetric.NewManualReader() meterProvider := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) if _, err := w.Write(responseBody); err != nil { t.Fatal(err) } })) defer ts.Close() r, err := http.NewRequest(http.MethodGet, ts.URL, bytes.NewReader(requestBody)) if err != nil { t.Fatal(err) } tr := otelhttp.NewTransport( http.DefaultTransport, otelhttp.WithMeterProvider(meterProvider), ) c := http.Client{Transport: tr} res, err := c.Do(r) if err != nil { t.Fatal(err) } // Must read the body or else we won't get response metrics bodyBytes, err := io.ReadAll(res.Body) if err != nil { t.Fatal(err) } require.Len(t, bodyBytes, 13) require.NoError(t, res.Body.Close()) host, portStr, _ := net.SplitHostPort(r.Host) if host == "" { host = "127.0.0.1" } port, err := strconv.Atoi(portStr) if err != nil { port = 0 } rm := metricdata.ResourceMetrics{} err = reader.Collect(context.Background(), &rm) require.NoError(t, err) require.Len(t, rm.ScopeMetrics, 1) attrs := attribute.NewSet( semconv.NetPeerName(host), semconv.NetPeerPort(port), semconv.HTTPMethod("GET"), semconv.HTTPStatusCode(200), ) assertClientScopeMetrics(t, rm.ScopeMetrics[0], attrs, 13) }) t.Run("make http request and buffer response", func(t *testing.T) { reader := sdkmetric.NewManualReader() meterProvider := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) if _, err := w.Write(responseBody); err != nil { t.Fatal(err) } })) defer ts.Close() r, err := http.NewRequest(http.MethodGet, ts.URL, bytes.NewReader(requestBody)) if err != nil { t.Fatal(err) } tr := otelhttp.NewTransport( http.DefaultTransport, otelhttp.WithMeterProvider(meterProvider), ) c := http.Client{Transport: tr} res, err := c.Do(r) if err != nil { t.Fatal(err) } // Must read the body or else we won't get response metrics smallBuf := make([]byte, 10) // Read first 10 bytes bc, err := res.Body.Read(smallBuf) if err != nil { t.Fatal(err) } require.Equal(t, 10, bc) // reset byte array // Read last 3 bytes bc, err = res.Body.Read(smallBuf) require.Equal(t, io.EOF, err) require.Equal(t, 3, bc) require.NoError(t, res.Body.Close()) host, portStr, _ := net.SplitHostPort(r.Host) if host == "" { host = "127.0.0.1" } port, err := strconv.Atoi(portStr) if err != nil { port = 0 } rm := metricdata.ResourceMetrics{} err = reader.Collect(context.Background(), &rm) require.NoError(t, err) require.Len(t, rm.ScopeMetrics, 1) attrs := attribute.NewSet( semconv.NetPeerName(host), semconv.NetPeerPort(port), semconv.HTTPMethod("GET"), semconv.HTTPStatusCode(200), ) assertClientScopeMetrics(t, rm.ScopeMetrics[0], attrs, 13) }) t.Run("make http request and close body before reading completely", func(t *testing.T) { reader := sdkmetric.NewManualReader() meterProvider := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) if _, err := w.Write(responseBody); err != nil { t.Fatal(err) } })) defer ts.Close() r, err := http.NewRequest(http.MethodGet, ts.URL, bytes.NewReader(requestBody)) if err != nil { t.Fatal(err) } tr := otelhttp.NewTransport( http.DefaultTransport, otelhttp.WithMeterProvider(meterProvider), ) c := http.Client{Transport: tr} res, err := c.Do(r) if err != nil { t.Fatal(err) } // Must read the body or else we won't get response metrics smallBuf := make([]byte, 10) // Read first 10 bytes bc, err := res.Body.Read(smallBuf) if err != nil { t.Fatal(err) } require.Equal(t, 10, bc) // close the response body early require.NoError(t, res.Body.Close()) host, portStr, _ := net.SplitHostPort(r.Host) if host == "" { host = "127.0.0.1" } port, err := strconv.Atoi(portStr) if err != nil { port = 0 } rm := metricdata.ResourceMetrics{} err = reader.Collect(context.Background(), &rm) require.NoError(t, err) require.Len(t, rm.ScopeMetrics, 1) attrs := attribute.NewSet( semconv.NetPeerName(host), semconv.NetPeerPort(port), semconv.HTTPMethod("GET"), semconv.HTTPStatusCode(200), ) assertClientScopeMetrics(t, rm.ScopeMetrics[0], attrs, 10) }) } func assertClientScopeMetrics(t *testing.T, sm metricdata.ScopeMetrics, attrs attribute.Set, rxBytes int64) { assert.Equal(t, instrumentation.Scope{ Name: "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp", Version: Version(), }, sm.Scope) require.Len(t, sm.Metrics, 3) want := metricdata.Metrics{ Name: "http.client.request.size", Data: metricdata.Sum[int64]{ DataPoints: []metricdata.DataPoint[int64]{{Attributes: attrs, Value: 4}}, Temporality: metricdata.CumulativeTemporality, IsMonotonic: true, }, Description: "Measures the size of HTTP request messages.", Unit: "By", } metricdatatest.AssertEqual(t, want, sm.Metrics[0], metricdatatest.IgnoreTimestamp()) want = metricdata.Metrics{ Name: "http.client.response.size", Data: metricdata.Sum[int64]{ DataPoints: []metricdata.DataPoint[int64]{{Attributes: attrs, Value: rxBytes}}, Temporality: metricdata.CumulativeTemporality, IsMonotonic: true, }, Description: "Measures the size of HTTP response messages.", Unit: "By", } metricdatatest.AssertEqual(t, want, sm.Metrics[1], metricdatatest.IgnoreTimestamp()) want = metricdata.Metrics{ Name: "http.client.duration", Data: metricdata.Histogram[float64]{ DataPoints: []metricdata.HistogramDataPoint[float64]{{Attributes: attrs}}, Temporality: metricdata.CumulativeTemporality, }, Description: "Measures the duration of outbound HTTP requests.", Unit: "ms", } metricdatatest.AssertEqual(t, want, sm.Metrics[2], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue()) } func TestCustomAttributesHandling(t *testing.T) { var rm metricdata.ResourceMetrics const ( clientRequestSize = "http.client.request.size" clientDuration = "http.client.duration" ) ctx := context.TODO() reader := sdkmetric.NewManualReader() provider := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) defer func() { err := provider.Shutdown(ctx) if err != nil { t.Errorf("Error shutting down provider: %v", err) } }() transport := otelhttp.NewTransport(http.DefaultTransport, otelhttp.WithMeterProvider(provider)) client := http.Client{Transport: transport} ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) })) defer ts.Close() expectedAttributes := []attribute.KeyValue{ attribute.String("foo", "fooValue"), attribute.String("bar", "barValue"), } r, err := http.NewRequest(http.MethodGet, ts.URL, nil) require.NoError(t, err) labeler := &otelhttp.Labeler{} labeler.Add(expectedAttributes...) ctx = otelhttp.ContextWithLabeler(ctx, labeler) r = r.WithContext(ctx) // test bonus: intententionally ignoring response to confirm that // http.client.response.size metric is not recorded // by the Transport.RoundTrip logic resp, err := client.Do(r) require.NoError(t, err) defer func() { assert.NoError(t, resp.Body.Close()) }() err = reader.Collect(ctx, &rm) assert.NoError(t, err) // http.client.response.size is not recorded so the assert.Len // above should be 2 instead of 3(test bonus) assert.Len(t, rm.ScopeMetrics[0].Metrics, 2) for _, m := range rm.ScopeMetrics[0].Metrics { switch m.Name { case clientRequestSize: d, ok := m.Data.(metricdata.Sum[int64]) assert.True(t, ok) assert.Len(t, d.DataPoints, 1) containsAttributes(t, d.DataPoints[0].Attributes, expectedAttributes) case clientDuration: d, ok := m.Data.(metricdata.Histogram[float64]) assert.True(t, ok) assert.Len(t, d.DataPoints, 1) containsAttributes(t, d.DataPoints[0].Attributes, expectedAttributes) } } } func TestDefaultAttributesHandling(t *testing.T) { var rm metricdata.ResourceMetrics const ( clientRequestSize = "http.client.request.size" clientDuration = "http.client.duration" ) ctx := context.TODO() reader := sdkmetric.NewManualReader() provider := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) defer func() { err := provider.Shutdown(ctx) if err != nil { t.Errorf("Error shutting down provider: %v", err) } }() defaultAttributes := []attribute.KeyValue{ attribute.String("defaultFoo", "fooValue"), attribute.String("defaultBar", "barValue"), } transport := otelhttp.NewTransport( http.DefaultTransport, otelhttp.WithMeterProvider(provider), otelhttp.WithMetricAttributesFn(func(_ *http.Request) []attribute.KeyValue { return defaultAttributes })) client := http.Client{Transport: transport} ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) })) defer ts.Close() r, err := http.NewRequest(http.MethodGet, ts.URL, nil) require.NoError(t, err) resp, err := client.Do(r) require.NoError(t, err) _ = resp.Body.Close() err = reader.Collect(ctx, &rm) assert.NoError(t, err) assert.Len(t, rm.ScopeMetrics[0].Metrics, 3) for _, m := range rm.ScopeMetrics[0].Metrics { switch m.Name { case clientRequestSize: d, ok := m.Data.(metricdata.Sum[int64]) assert.True(t, ok) assert.Len(t, d.DataPoints, 1) containsAttributes(t, d.DataPoints[0].Attributes, defaultAttributes) case clientDuration: d, ok := m.Data.(metricdata.Histogram[float64]) assert.True(t, ok) assert.Len(t, d.DataPoints, 1) containsAttributes(t, d.DataPoints[0].Attributes, defaultAttributes) } } } func containsAttributes(t *testing.T, attrSet attribute.Set, expected []attribute.KeyValue) { for _, att := range expected { actualValue, ok := attrSet.Value(att.Key) assert.True(t, ok) assert.Equal(t, att.Value.AsString(), actualValue.AsString()) } } func BenchmarkTransportRoundTrip(b *testing.B) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "Hello World") })) defer ts.Close() tp := sdktrace.NewTracerProvider() mp := sdkmetric.NewMeterProvider() r, err := http.NewRequest(http.MethodGet, ts.URL, nil) require.NoError(b, err) for _, bb := range []struct { name string transport http.RoundTripper }{ { name: "without the otelhttp transport", transport: http.DefaultTransport, }, { name: "with the otelhttp transport", transport: otelhttp.NewTransport( http.DefaultTransport, otelhttp.WithTracerProvider(tp), otelhttp.WithMeterProvider(mp), ), }, } { b.Run(bb.name, func(b *testing.B) { c := http.Client{Transport: bb.transport} b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { resp, _ := c.Do(r) resp.Body.Close() } }) } } open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/test/version.go000066400000000000000000000010351470323427300341160ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package test // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/test" // Version is the current release version of the otelhttp instrumentation test module. func Version() string { return "0.56.0" // This string is updated by the pre_release.sh script during release } // SemVersion is the semantic version to be supplied to tracer/meter creation. // // Deprecated: Use [Version] instead. func SemVersion() string { return Version() } open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/transport.go000066400000000000000000000202221470323427300335050ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" import ( "context" "io" "net/http" "net/http/httptrace" "sync/atomic" "time" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/request" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" ) // Transport implements the http.RoundTripper interface and wraps // outbound HTTP(S) requests with a span and enriches it with metrics. type Transport struct { rt http.RoundTripper tracer trace.Tracer propagators propagation.TextMapPropagator spanStartOptions []trace.SpanStartOption filters []Filter spanNameFormatter func(string, *http.Request) string clientTrace func(context.Context) *httptrace.ClientTrace metricAttributesFn func(*http.Request) []attribute.KeyValue semconv semconv.HTTPClient } var _ http.RoundTripper = &Transport{} // NewTransport wraps the provided http.RoundTripper with one that // starts a span, injects the span context into the outbound request headers, // and enriches it with metrics. // // If the provided http.RoundTripper is nil, http.DefaultTransport will be used // as the base http.RoundTripper. func NewTransport(base http.RoundTripper, opts ...Option) *Transport { if base == nil { base = http.DefaultTransport } t := Transport{ rt: base, } defaultOpts := []Option{ WithSpanOptions(trace.WithSpanKind(trace.SpanKindClient)), WithSpanNameFormatter(defaultTransportFormatter), } c := newConfig(append(defaultOpts, opts...)...) t.applyConfig(c) return &t } func (t *Transport) applyConfig(c *config) { t.tracer = c.Tracer t.propagators = c.Propagators t.spanStartOptions = c.SpanStartOptions t.filters = c.Filters t.spanNameFormatter = c.SpanNameFormatter t.clientTrace = c.ClientTrace t.semconv = semconv.NewHTTPClient(c.Meter) t.metricAttributesFn = c.MetricAttributesFn } func defaultTransportFormatter(_ string, r *http.Request) string { return "HTTP " + r.Method } // RoundTrip creates a Span and propagates its context via the provided request's headers // before handing the request to the configured base RoundTripper. The created span will // end when the response body is closed or when a read from the body returns io.EOF. func (t *Transport) RoundTrip(r *http.Request) (*http.Response, error) { requestStartTime := time.Now() for _, f := range t.filters { if !f(r) { // Simply pass through to the base RoundTripper if a filter rejects the request return t.rt.RoundTrip(r) } } tracer := t.tracer if tracer == nil { if span := trace.SpanFromContext(r.Context()); span.SpanContext().IsValid() { tracer = newTracer(span.TracerProvider()) } else { tracer = newTracer(otel.GetTracerProvider()) } } opts := append([]trace.SpanStartOption{}, t.spanStartOptions...) // start with the configured options ctx, span := tracer.Start(r.Context(), t.spanNameFormatter("", r), opts...) if t.clientTrace != nil { ctx = httptrace.WithClientTrace(ctx, t.clientTrace(ctx)) } labeler, found := LabelerFromContext(ctx) if !found { ctx = ContextWithLabeler(ctx, labeler) } r = r.Clone(ctx) // According to RoundTripper spec, we shouldn't modify the origin request. // if request body is nil or NoBody, we don't want to mutate the body as it // will affect the identity of it in an unforeseeable way because we assert // ReadCloser fulfills a certain interface and it is indeed nil or NoBody. bw := request.NewBodyWrapper(r.Body, func(int64) {}) if r.Body != nil && r.Body != http.NoBody { r.Body = bw } span.SetAttributes(t.semconv.RequestTraceAttrs(r)...) t.propagators.Inject(ctx, propagation.HeaderCarrier(r.Header)) res, err := t.rt.RoundTrip(r) if err != nil { // set error type attribute if the error is part of the predefined // error types. // otherwise, record it as an exception if errType := t.semconv.ErrorType(err); errType.Valid() { span.SetAttributes(errType) } else { span.RecordError(err) } span.SetStatus(codes.Error, err.Error()) span.End() return res, err } // metrics metricOpts := t.semconv.MetricOptions(semconv.MetricAttributes{ Req: r, StatusCode: res.StatusCode, AdditionalAttributes: append(labeler.Get(), t.metricAttributesFromRequest(r)...), }) // For handling response bytes we leverage a callback when the client reads the http response readRecordFunc := func(n int64) { t.semconv.RecordResponseSize(ctx, n, metricOpts.AddOptions()) } // traces span.SetAttributes(t.semconv.ResponseTraceAttrs(res)...) span.SetStatus(t.semconv.Status(res.StatusCode)) res.Body = newWrappedBody(span, readRecordFunc, res.Body) // Use floating point division here for higher precision (instead of Millisecond method). elapsedTime := float64(time.Since(requestStartTime)) / float64(time.Millisecond) t.semconv.RecordMetrics(ctx, semconv.MetricData{ RequestSize: bw.BytesRead(), ElapsedTime: elapsedTime, }, metricOpts) return res, nil } func (t *Transport) metricAttributesFromRequest(r *http.Request) []attribute.KeyValue { var attributeForRequest []attribute.KeyValue if t.metricAttributesFn != nil { attributeForRequest = t.metricAttributesFn(r) } return attributeForRequest } // newWrappedBody returns a new and appropriately scoped *wrappedBody as an // io.ReadCloser. If the passed body implements io.Writer, the returned value // will implement io.ReadWriteCloser. func newWrappedBody(span trace.Span, record func(n int64), body io.ReadCloser) io.ReadCloser { // The successful protocol switch responses will have a body that // implement an io.ReadWriteCloser. Ensure this interface type continues // to be satisfied if that is the case. if _, ok := body.(io.ReadWriteCloser); ok { return &wrappedBody{span: span, record: record, body: body} } // Remove the implementation of the io.ReadWriteCloser and only implement // the io.ReadCloser. return struct{ io.ReadCloser }{&wrappedBody{span: span, record: record, body: body}} } // wrappedBody is the response body type returned by the transport // instrumentation to complete a span. Errors encountered when using the // response body are recorded in span tracking the response. // // The span tracking the response is ended when this body is closed. // // If the response body implements the io.Writer interface (i.e. for // successful protocol switches), the wrapped body also will. type wrappedBody struct { span trace.Span recorded atomic.Bool record func(n int64) body io.ReadCloser read atomic.Int64 } var _ io.ReadWriteCloser = &wrappedBody{} func (wb *wrappedBody) Write(p []byte) (int, error) { // This will not panic given the guard in newWrappedBody. n, err := wb.body.(io.Writer).Write(p) if err != nil { wb.span.RecordError(err) wb.span.SetStatus(codes.Error, err.Error()) } return n, err } func (wb *wrappedBody) Read(b []byte) (int, error) { n, err := wb.body.Read(b) // Record the number of bytes read wb.read.Add(int64(n)) switch err { case nil: // nothing to do here but fall through to the return case io.EOF: wb.recordBytesRead() wb.span.End() default: wb.span.RecordError(err) wb.span.SetStatus(codes.Error, err.Error()) } return n, err } // recordBytesRead is a function that ensures the number of bytes read is recorded once and only once. func (wb *wrappedBody) recordBytesRead() { // note: it is more performant (and equally correct) to use atomic.Bool over sync.Once here. In the event that // two goroutines are racing to call this method, the number of bytes read will no longer increase. Using // CompareAndSwap allows later goroutines to return quickly and not block waiting for the race winner to finish // calling wb.record(wb.read.Load()). if wb.recorded.CompareAndSwap(false, true) { // Record the total number of bytes read wb.record(wb.read.Load()) } } func (wb *wrappedBody) Close() error { wb.recordBytesRead() wb.span.End() if wb.body != nil { return wb.body.Close() } return nil } transport_example_test.go000066400000000000000000000005071470323427300362040ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelhttp import ( "net/http" ) func ExampleNewTransport() { // Create an http.Client that uses the (ot)http.Transport // wrapped around the http.DefaultTransport _ = http.Client{ Transport: NewTransport(http.DefaultTransport), } } open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/transport_test.go000066400000000000000000000251211470323427300345470ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelhttp import ( "bytes" "context" "errors" "io" "net/http" "net/http/httptest" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" ) func TestTransportFormatter(t *testing.T) { httpMethods := []struct { name string method string expected string }{ { "GET method", http.MethodGet, "HTTP GET", }, { "HEAD method", http.MethodHead, "HTTP HEAD", }, { "POST method", http.MethodPost, "HTTP POST", }, { "PUT method", http.MethodPut, "HTTP PUT", }, { "PATCH method", http.MethodPatch, "HTTP PATCH", }, { "DELETE method", http.MethodDelete, "HTTP DELETE", }, { "CONNECT method", http.MethodConnect, "HTTP CONNECT", }, { "OPTIONS method", http.MethodOptions, "HTTP OPTIONS", }, { "TRACE method", http.MethodTrace, "HTTP TRACE", }, } for _, tc := range httpMethods { t.Run(tc.name, func(t *testing.T) { r, err := http.NewRequest(tc.method, "http://localhost/", nil) if err != nil { t.Fatal(err) } formattedName := "HTTP " + r.Method if formattedName != tc.expected { t.Fatalf("unexpected name: got %s, expected %s", formattedName, tc.expected) } }) } } func TestTransportBasics(t *testing.T) { prop := propagation.TraceContext{} content := []byte("Hello, world!") ctx := context.Background() sc := trace.NewSpanContext(trace.SpanContextConfig{ TraceID: trace.TraceID{0x01}, SpanID: trace.SpanID{0x01}, }) ctx = trace.ContextWithRemoteSpanContext(ctx, sc) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := prop.Extract(r.Context(), propagation.HeaderCarrier(r.Header)) span := trace.SpanContextFromContext(ctx) if span.SpanID() != sc.SpanID() { t.Fatalf("testing remote SpanID: got %s, expected %s", span.SpanID(), sc.SpanID()) } if _, err := w.Write(content); err != nil { t.Fatal(err) } })) defer ts.Close() r, err := http.NewRequestWithContext(ctx, http.MethodGet, ts.URL, nil) if err != nil { t.Fatal(err) } tr := NewTransport(http.DefaultTransport, WithPropagators(prop)) c := http.Client{Transport: tr} res, err := c.Do(r) if err != nil { t.Fatal(err) } defer func() { if err := res.Body.Close(); err != nil { t.Errorf("close response body: %v", err) } }() body, err := io.ReadAll(res.Body) if err != nil { t.Fatal(err) } if !bytes.Equal(body, content) { t.Fatalf("unexpected content: got %s, expected %s", body, content) } } func TestNilTransport(t *testing.T) { prop := propagation.TraceContext{} content := []byte("Hello, world!") ctx := context.Background() sc := trace.NewSpanContext(trace.SpanContextConfig{ TraceID: trace.TraceID{0x01}, SpanID: trace.SpanID{0x01}, }) ctx = trace.ContextWithRemoteSpanContext(ctx, sc) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := prop.Extract(r.Context(), propagation.HeaderCarrier(r.Header)) span := trace.SpanContextFromContext(ctx) if span.SpanID() != sc.SpanID() { t.Fatalf("testing remote SpanID: got %s, expected %s", span.SpanID(), sc.SpanID()) } if _, err := w.Write(content); err != nil { t.Fatal(err) } })) defer ts.Close() r, err := http.NewRequestWithContext(ctx, http.MethodGet, ts.URL, nil) if err != nil { t.Fatal(err) } tr := NewTransport(nil, WithPropagators(prop)) c := http.Client{Transport: tr} res, err := c.Do(r) if err != nil { t.Fatal(err) } defer func() { if err := res.Body.Close(); err != nil { t.Errorf("close response body: %v", err) } }() body, err := io.ReadAll(res.Body) if err != nil { t.Fatal(err) } if !bytes.Equal(body, content) { t.Fatalf("unexpected content: got %s, expected %s", body, content) } } const readSize = 42 type readCloser struct { readErr, closeErr error } func (rc readCloser) Read(p []byte) (n int, err error) { return readSize, rc.readErr } func (rc readCloser) Close() error { return rc.closeErr } type span struct { trace.Span ended bool recordedErr error statusCode codes.Code statusDesc string } func (s *span) End(...trace.SpanEndOption) { s.ended = true } func (s *span) RecordError(err error, _ ...trace.EventOption) { s.recordedErr = err } func (s *span) SetStatus(c codes.Code, d string) { s.statusCode, s.statusDesc = c, d } func (s *span) assert(t *testing.T, ended bool, err error, c codes.Code, d string) { // nolint: revive // ended is not a control flag. if ended { assert.True(t, s.ended, "not ended") } else { assert.False(t, s.ended, "ended") } if err == nil { assert.NoError(t, s.recordedErr, "recorded an error") } else { assert.Equal(t, err, s.recordedErr) } assert.Equal(t, c, s.statusCode, "status codes not equal") assert.Equal(t, d, s.statusDesc, "status description not equal") } func TestWrappedBodyRead(t *testing.T) { s := new(span) called := false record := func(numBytes int64) { called = true } wb := newWrappedBody(s, record, readCloser{}) n, err := wb.Read([]byte{}) assert.Equal(t, readSize, n, "wrappedBody returned wrong bytes") assert.NoError(t, err) s.assert(t, false, nil, codes.Unset, "") assert.False(t, called, "record should not have been called") } func TestWrappedBodyReadEOFError(t *testing.T) { s := new(span) called := false numRecorded := int64(0) record := func(numBytes int64) { called = true numRecorded = numBytes } wb := newWrappedBody(s, record, readCloser{readErr: io.EOF}) n, err := wb.Read([]byte{}) assert.Equal(t, readSize, n, "wrappedBody returned wrong bytes") assert.Equal(t, io.EOF, err) s.assert(t, true, nil, codes.Unset, "") assert.True(t, called, "record should have been called") assert.Equal(t, int64(readSize), numRecorded, "record recorded wrong number of bytes") } func TestWrappedBodyReadError(t *testing.T) { s := new(span) called := false record := func(int64) { called = true } expectedErr := errors.New("test") wb := newWrappedBody(s, record, readCloser{readErr: expectedErr}) n, err := wb.Read([]byte{}) assert.Equal(t, readSize, n, "wrappedBody returned wrong bytes") assert.Equal(t, expectedErr, err) s.assert(t, false, expectedErr, codes.Error, expectedErr.Error()) assert.False(t, called, "record should not have been called") } func TestWrappedBodyClose(t *testing.T) { s := new(span) called := false record := func(int64) { called = true } wb := newWrappedBody(s, record, readCloser{}) assert.NoError(t, wb.Close()) s.assert(t, true, nil, codes.Unset, "") assert.True(t, called, "record should have been called") } func TestWrappedBodyClosePanic(t *testing.T) { s := new(span) var body io.ReadCloser wb := newWrappedBody(s, func(n int64) {}, body) assert.NotPanics(t, func() { wb.Close() }, "nil body should not panic on close") } func TestWrappedBodyCloseError(t *testing.T) { s := new(span) called := false record := func(int64) { called = true } expectedErr := errors.New("test") wb := newWrappedBody(s, record, readCloser{closeErr: expectedErr}) assert.Equal(t, expectedErr, wb.Close()) s.assert(t, true, nil, codes.Unset, "") assert.True(t, called, "record should have been called") } type readWriteCloser struct { readCloser writeErr error } const writeSize = 1 func (rwc readWriteCloser) Write([]byte) (int, error) { return writeSize, rwc.writeErr } func TestNewWrappedBodyReadWriteCloserImplementation(t *testing.T) { wb := newWrappedBody(nil, func(n int64) {}, readWriteCloser{}) assert.Implements(t, (*io.ReadWriteCloser)(nil), wb) } func TestNewWrappedBodyReadCloserImplementation(t *testing.T) { wb := newWrappedBody(nil, func(n int64) {}, readCloser{}) assert.Implements(t, (*io.ReadCloser)(nil), wb) _, ok := wb.(io.ReadWriteCloser) assert.False(t, ok, "wrappedBody should not implement io.ReadWriteCloser") } func TestWrappedBodyWrite(t *testing.T) { s := new(span) var rwc io.ReadWriteCloser assert.NotPanics(t, func() { rwc = newWrappedBody(s, func(n int64) {}, readWriteCloser{}).(io.ReadWriteCloser) }) n, err := rwc.Write([]byte{}) assert.Equal(t, writeSize, n, "wrappedBody returned wrong bytes") assert.NoError(t, err) s.assert(t, false, nil, codes.Unset, "") } func TestWrappedBodyWriteError(t *testing.T) { s := new(span) expectedErr := errors.New("test") var rwc io.ReadWriteCloser assert.NotPanics(t, func() { rwc = newWrappedBody(s, func(n int64) {}, readWriteCloser{ writeErr: expectedErr, }).(io.ReadWriteCloser) }) n, err := rwc.Write([]byte{}) assert.Equal(t, writeSize, n, "wrappedBody returned wrong bytes") assert.ErrorIs(t, err, expectedErr) s.assert(t, false, expectedErr, codes.Error, expectedErr.Error()) } func TestTransportProtocolSwitch(t *testing.T) { // This test validates the fix to #1329. // Simulate a "101 Switching Protocols" response from the test server. response := []byte(strings.Join([]string{ "HTTP/1.1 101 Switching Protocols", "Upgrade: WebSocket", "Connection: Upgrade", "", "", // Needed for extra CRLF. }, "\r\n")) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { conn, buf, err := w.(http.Hijacker).Hijack() assert.NoError(t, err) _, err = buf.Write(response) assert.NoError(t, err) assert.NoError(t, buf.Flush()) assert.NoError(t, conn.Close()) })) defer ts.Close() ctx := context.Background() r, err := http.NewRequestWithContext(ctx, http.MethodGet, ts.URL, http.NoBody) require.NoError(t, err) c := http.Client{Transport: NewTransport(http.DefaultTransport)} res, err := c.Do(r) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, res.Body.Close()) }) assert.Implements(t, (*io.ReadWriteCloser)(nil), res.Body, "invalid body returned for protocol switch") } func TestTransportOriginRequestNotModify(t *testing.T) { prop := propagation.TraceContext{} ctx := context.Background() sc := trace.NewSpanContext(trace.SpanContextConfig{ TraceID: trace.TraceID{0x01}, SpanID: trace.SpanID{0x01}, }) ctx = trace.ContextWithRemoteSpanContext(ctx, sc) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) })) defer ts.Close() r, err := http.NewRequestWithContext(ctx, http.MethodGet, ts.URL, http.NoBody) require.NoError(t, err) expectedRequest := r.Clone(r.Context()) c := http.Client{Transport: NewTransport(http.DefaultTransport, WithPropagators(prop))} res, err := c.Do(r) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, res.Body.Close()) }) assert.Equal(t, expectedRequest, r) } open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/net/http/otelhttp/version.go000066400000000000000000000010201470323427300331310ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" // Version is the current release version of the otelhttp instrumentation. func Version() string { return "0.56.0" // This string is updated by the pre_release.sh script during release } // SemVersion is the semantic version to be supplied to tracer/meter creation. // // Deprecated: Use [Version] instead. func SemVersion() string { return Version() } open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/runtime/000077500000000000000000000000001470323427300271775ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/runtime/doc.go000066400000000000000000000045571470323427300303060ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package runtime implements the conventional runtime metrics specified by OpenTelemetry. // // The metric events produced are: // // runtime.go.cgo.calls - Number of cgo calls made by the current process // runtime.go.gc.count - Number of completed garbage collection cycles // runtime.go.gc.pause_ns (ns) Amount of nanoseconds in GC stop-the-world pauses // runtime.go.gc.pause_total_ns (ns) Cumulative nanoseconds in GC stop-the-world pauses since the program started // runtime.go.goroutines - Number of goroutines that currently exist // runtime.go.lookups - Number of pointer lookups performed by the runtime // runtime.go.mem.heap_alloc (bytes) Bytes of allocated heap objects // runtime.go.mem.heap_idle (bytes) Bytes in idle (unused) spans // runtime.go.mem.heap_inuse (bytes) Bytes in in-use spans // runtime.go.mem.heap_objects - Number of allocated heap objects // runtime.go.mem.heap_released (bytes) Bytes of idle spans whose physical memory has been returned to the OS // runtime.go.mem.heap_sys (bytes) Bytes of heap memory obtained from the OS // runtime.go.mem.live_objects - Number of live objects is the number of cumulative Mallocs - Frees // runtime.uptime (ms) Milliseconds since application was initialized // // When the OTEL_GO_X_DEPRECATED_RUNTIME_METRICS environment variable is set to // false, the metrics produced are: // // go.memory.used By Memory used by the Go runtime. // go.memory.limit By Go runtime memory limit configured by the user, if a limit exists. // go.memory.allocated By Memory allocated to the heap by the application. // go.memory.allocations {allocation} Count of allocations to the heap by the application. // go.memory.gc.goal By Heap size target for the end of the GC cycle. // go.goroutine.count {goroutine} Count of live goroutines. // go.processor.limit {thread} The number of OS threads that can execute user-level Go code simultaneously. // go.config.gogc % Heap size target percentage configured by the user, otherwise 100. package runtime // import "go.opentelemetry.io/contrib/instrumentation/runtime" open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/runtime/example_test.go000066400000000000000000000017431470323427300322250ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package runtime_test import ( "context" "log" "time" "go.opentelemetry.io/contrib/instrumentation/runtime" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/sdk/metric" ) func Example() { // This reader is used as a stand-in for a reader that will actually export // data. See https://pkg.go.dev/go.opentelemetry.io/otel/exporters for // exporters that can be used as or with readers. reader := metric.NewManualReader( // Add the runtime producer to get histograms from the Go runtime. metric.WithProducer(runtime.NewProducer()), ) provider := metric.NewMeterProvider(metric.WithReader(reader)) defer func() { err := provider.Shutdown(context.Background()) if err != nil { log.Fatal(err) } }() otel.SetMeterProvider(provider) // Start go runtime metric collection. err := runtime.Start(runtime.WithMinimumReadMemStatsInterval(time.Second)) if err != nil { log.Fatal(err) } } open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/runtime/go.mod000066400000000000000000000012031470323427300303010ustar00rootroot00000000000000module go.opentelemetry.io/contrib/instrumentation/runtime go 1.22 require ( github.com/stretchr/testify v1.9.0 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/metric v1.31.0 go.opentelemetry.io/otel/sdk v1.31.0 go.opentelemetry.io/otel/sdk/metric v1.31.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/otel/trace v1.31.0 // indirect golang.org/x/sys v0.26.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/runtime/go.sum000066400000000000000000000051711470323427300303360ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc= go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/runtime/internal/000077500000000000000000000000001470323427300310135ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/runtime/internal/deprecatedruntime/000077500000000000000000000000001470323427300345175ustar00rootroot00000000000000doc.go000066400000000000000000000030611470323427300355340ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/runtime/internal/deprecatedruntime// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package deprecatedruntime implements the deprecated runtime metrics for OpenTelemetry. // // The metric events produced are: // // runtime.go.cgo.calls - Number of cgo calls made by the current process // runtime.go.gc.count - Number of completed garbage collection cycles // runtime.go.gc.pause_ns (ns) Amount of nanoseconds in GC stop-the-world pauses // runtime.go.gc.pause_total_ns (ns) Cumulative nanoseconds in GC stop-the-world pauses since the program started // runtime.go.goroutines - Number of goroutines that currently exist // runtime.go.lookups - Number of pointer lookups performed by the runtime // runtime.go.mem.heap_alloc (bytes) Bytes of allocated heap objects // runtime.go.mem.heap_idle (bytes) Bytes in idle (unused) spans // runtime.go.mem.heap_inuse (bytes) Bytes in in-use spans // runtime.go.mem.heap_objects - Number of allocated heap objects // runtime.go.mem.heap_released (bytes) Bytes of idle spans whose physical memory has been returned to the OS // runtime.go.mem.heap_sys (bytes) Bytes of heap memory obtained from the OS // runtime.go.mem.live_objects - Number of live objects is the number of cumulative Mallocs - Frees // runtime.uptime (ms) Milliseconds since application was initialized package deprecatedruntime // import "go.opentelemetry.io/contrib/instrumentation/runtime/internal/deprecatedruntime" runtime.go000066400000000000000000000171731470323427300364630ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/runtime/internal/deprecatedruntime// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package deprecatedruntime // import "go.opentelemetry.io/contrib/instrumentation/runtime/internal/deprecatedruntime" import ( "context" "math" goruntime "runtime" "sync" "time" "go.opentelemetry.io/otel/metric" ) // Runtime reports the work-in-progress conventional runtime metrics specified by OpenTelemetry. type runtime struct { minimumReadMemStatsInterval time.Duration meter metric.Meter } // Start initializes reporting of runtime metrics using the supplied config. func Start(meter metric.Meter, minimumReadMemStatsInterval time.Duration) error { r := &runtime{ meter: meter, minimumReadMemStatsInterval: minimumReadMemStatsInterval, } return r.register() } func (r *runtime) register() error { startTime := time.Now() uptime, err := r.meter.Int64ObservableCounter( "runtime.uptime", metric.WithUnit("ms"), metric.WithDescription("Milliseconds since application was initialized"), ) if err != nil { return err } goroutines, err := r.meter.Int64ObservableUpDownCounter( "process.runtime.go.goroutines", metric.WithDescription("Number of goroutines that currently exist"), ) if err != nil { return err } cgoCalls, err := r.meter.Int64ObservableUpDownCounter( "process.runtime.go.cgo.calls", metric.WithDescription("Number of cgo calls made by the current process"), ) if err != nil { return err } _, err = r.meter.RegisterCallback( func(ctx context.Context, o metric.Observer) error { o.ObserveInt64(uptime, time.Since(startTime).Milliseconds()) o.ObserveInt64(goroutines, int64(goruntime.NumGoroutine())) o.ObserveInt64(cgoCalls, goruntime.NumCgoCall()) return nil }, uptime, goroutines, cgoCalls, ) if err != nil { return err } return r.registerMemStats() } func (r *runtime) registerMemStats() error { var ( err error heapAlloc metric.Int64ObservableUpDownCounter heapIdle metric.Int64ObservableUpDownCounter heapInuse metric.Int64ObservableUpDownCounter heapObjects metric.Int64ObservableUpDownCounter heapReleased metric.Int64ObservableUpDownCounter heapSys metric.Int64ObservableUpDownCounter liveObjects metric.Int64ObservableUpDownCounter // TODO: is ptrLookups useful? I've not seen a value // other than zero. ptrLookups metric.Int64ObservableCounter gcCount metric.Int64ObservableCounter pauseTotalNs metric.Int64ObservableCounter gcPauseNs metric.Int64Histogram lastNumGC uint32 lastMemStats time.Time memStats goruntime.MemStats // lock prevents a race between batch observer and instrument registration. lock sync.Mutex ) lock.Lock() defer lock.Unlock() if heapAlloc, err = r.meter.Int64ObservableUpDownCounter( "process.runtime.go.mem.heap_alloc", metric.WithUnit("By"), metric.WithDescription("Bytes of allocated heap objects"), ); err != nil { return err } if heapIdle, err = r.meter.Int64ObservableUpDownCounter( "process.runtime.go.mem.heap_idle", metric.WithUnit("By"), metric.WithDescription("Bytes in idle (unused) spans"), ); err != nil { return err } if heapInuse, err = r.meter.Int64ObservableUpDownCounter( "process.runtime.go.mem.heap_inuse", metric.WithUnit("By"), metric.WithDescription("Bytes in in-use spans"), ); err != nil { return err } if heapObjects, err = r.meter.Int64ObservableUpDownCounter( "process.runtime.go.mem.heap_objects", metric.WithDescription("Number of allocated heap objects"), ); err != nil { return err } // FYI see https://github.com/golang/go/issues/32284 to help // understand the meaning of this value. if heapReleased, err = r.meter.Int64ObservableUpDownCounter( "process.runtime.go.mem.heap_released", metric.WithUnit("By"), metric.WithDescription("Bytes of idle spans whose physical memory has been returned to the OS"), ); err != nil { return err } if heapSys, err = r.meter.Int64ObservableUpDownCounter( "process.runtime.go.mem.heap_sys", metric.WithUnit("By"), metric.WithDescription("Bytes of heap memory obtained from the OS"), ); err != nil { return err } if ptrLookups, err = r.meter.Int64ObservableCounter( "process.runtime.go.mem.lookups", metric.WithDescription("Number of pointer lookups performed by the runtime"), ); err != nil { return err } if liveObjects, err = r.meter.Int64ObservableUpDownCounter( "process.runtime.go.mem.live_objects", metric.WithDescription("Number of live objects is the number of cumulative Mallocs - Frees"), ); err != nil { return err } if gcCount, err = r.meter.Int64ObservableCounter( "process.runtime.go.gc.count", metric.WithDescription("Number of completed garbage collection cycles"), ); err != nil { return err } // Note that the following could be derived as a sum of // individual pauses, but we may lose individual pauses if the // observation interval is too slow. if pauseTotalNs, err = r.meter.Int64ObservableCounter( "process.runtime.go.gc.pause_total_ns", // TODO: nanoseconds units metric.WithDescription("Cumulative nanoseconds in GC stop-the-world pauses since the program started"), ); err != nil { return err } if gcPauseNs, err = r.meter.Int64Histogram( "process.runtime.go.gc.pause_ns", // TODO: nanoseconds units metric.WithDescription("Amount of nanoseconds in GC stop-the-world pauses"), ); err != nil { return err } _, err = r.meter.RegisterCallback( func(ctx context.Context, o metric.Observer) error { lock.Lock() defer lock.Unlock() now := time.Now() if now.Sub(lastMemStats) >= r.minimumReadMemStatsInterval { goruntime.ReadMemStats(&memStats) lastMemStats = now } o.ObserveInt64(heapAlloc, clampUint64(memStats.HeapAlloc)) o.ObserveInt64(heapIdle, clampUint64(memStats.HeapIdle)) o.ObserveInt64(heapInuse, clampUint64(memStats.HeapInuse)) o.ObserveInt64(heapObjects, clampUint64(memStats.HeapObjects)) o.ObserveInt64(heapReleased, clampUint64(memStats.HeapReleased)) o.ObserveInt64(heapSys, clampUint64(memStats.HeapSys)) o.ObserveInt64(liveObjects, clampUint64(memStats.Mallocs-memStats.Frees)) o.ObserveInt64(ptrLookups, clampUint64(memStats.Lookups)) o.ObserveInt64(gcCount, int64(memStats.NumGC)) o.ObserveInt64(pauseTotalNs, clampUint64(memStats.PauseTotalNs)) computeGCPauses(ctx, gcPauseNs, memStats.PauseNs[:], lastNumGC, memStats.NumGC) lastNumGC = memStats.NumGC return nil }, heapAlloc, heapIdle, heapInuse, heapObjects, heapReleased, heapSys, liveObjects, ptrLookups, gcCount, pauseTotalNs, ) if err != nil { return err } return nil } func clampUint64(v uint64) int64 { if v > math.MaxInt64 { return math.MaxInt64 } return int64(v) // nolint: gosec // Overflow checked above. } func computeGCPauses( ctx context.Context, recorder metric.Int64Histogram, circular []uint64, lastNumGC, currentNumGC uint32, ) { delta := int(int64(currentNumGC) - int64(lastNumGC)) if delta == 0 { return } if delta >= len(circular) { // There were > 256 collections, some may have been lost. recordGCPauses(ctx, recorder, circular) return } n := len(circular) if n < 0 { // Only the case in error situations. return } length := uint64(n) // nolint: gosec // n >= 0 i := uint64(lastNumGC) % length j := uint64(currentNumGC) % length if j < i { // wrap around the circular buffer recordGCPauses(ctx, recorder, circular[i:]) recordGCPauses(ctx, recorder, circular[:j]) return } recordGCPauses(ctx, recorder, circular[i:j]) } func recordGCPauses( ctx context.Context, recorder metric.Int64Histogram, pauses []uint64, ) { for _, pause := range pauses { recorder.Record(ctx, clampUint64(pause)) } } open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/runtime/internal/x/000077500000000000000000000000001470323427300312625ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/runtime/internal/x/README.md000066400000000000000000000026461470323427300325510ustar00rootroot00000000000000# Feature Gates The runtime package contains a feature gate used to ease the migration from the [previous runtime metrics conventions] to the new [OpenTelemetry Go Runtime conventions]. Note that the new runtime metrics conventions are still experimental, and may change in backwards incompatible ways as feedback is applied. ## Features - [Include Deprecated Metrics](#include-deprecated-metrics) ### Include Deprecated Metrics Once new experimental runtime metrics are added, they will be produced **in addition to** the existing runtime metrics. Users that migrate right away can disable the old runtime metrics: ```console export OTEL_GO_X_DEPRECATED_RUNTIME_METRICS=false ``` In a later release, the deprecated runtime metrics will stop being produced by default. To temporarily re-enable the deprecated metrics: ```console export OTEL_GO_X_DEPRECATED_RUNTIME_METRICS=true ``` After two additional releases, the deprecated runtime metrics will be removed, and setting the environment variable will no longer have any effect. The value set must be the case-insensitive string of `"true"` to enable the feature, and `"false"` to disable the feature. All other values are ignored. [previous runtime metrics conventions]: https://pkg.go.dev/go.opentelemetry.io/contrib/instrumentation/runtime@v0.52.0 [OpenTelemetry Go Runtime conventions]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/runtime/go-metrics.md open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/runtime/internal/x/x.go000066400000000000000000000032171470323427300320630ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package x contains support for OTel runtime instrumentation experimental features. // // This package should only be used for features defined in the specification. // It should not be used for experiments or new project ideas. package x // import "go.opentelemetry.io/contrib/instrumentation/runtime/internal/x" import ( "os" "strings" ) // DeprecatedRuntimeMetrics is an experimental feature flag that defines if the deprecated // runtime metrics should be produced. During development of the new // conventions, it is enabled by default. // // To disable this feature set the OTEL_GO_X_DEPRECATED_RUNTIME_METRICS environment variable // to the case-insensitive string value of "false" (i.e. "False" and "FALSE" // will also enable this). var DeprecatedRuntimeMetrics = newFeature("DEPRECATED_RUNTIME_METRICS", true) // BoolFeature is an experimental feature control flag. It provides a uniform way // to interact with these feature flags and parse their values. type BoolFeature struct { key string defaultVal bool } func newFeature(suffix string, defaultVal bool) BoolFeature { const envKeyRoot = "OTEL_GO_X_" return BoolFeature{ key: envKeyRoot + suffix, defaultVal: defaultVal, } } // Key returns the environment variable key that needs to be set to enable the // feature. func (f BoolFeature) Key() string { return f.key } // Enabled returns if the feature is enabled. func (f BoolFeature) Enabled() bool { v := os.Getenv(f.key) if strings.ToLower(v) == "false" { return false } if strings.ToLower(v) == "true" { return true } return f.defaultVal } open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/runtime/internal/x/x_test.go000066400000000000000000000026751470323427300331310ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package x import ( "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestDeprecatedRuntimeMetrics(t *testing.T) { const key = "OTEL_GO_X_DEPRECATED_RUNTIME_METRICS" require.Equal(t, key, DeprecatedRuntimeMetrics.Key()) t.Run("true", run(setenv(key, "true"), assertEnabled(DeprecatedRuntimeMetrics, true))) t.Run("True", run(setenv(key, "True"), assertEnabled(DeprecatedRuntimeMetrics, true))) t.Run("TRUE", run(setenv(key, "TRUE"), assertEnabled(DeprecatedRuntimeMetrics, true))) t.Run("false", run(setenv(key, "false"), assertEnabled(DeprecatedRuntimeMetrics, false))) t.Run("False", run(setenv(key, "False"), assertEnabled(DeprecatedRuntimeMetrics, false))) t.Run("FALSE", run(setenv(key, "FALSE"), assertEnabled(DeprecatedRuntimeMetrics, false))) t.Run("1", run(setenv(key, "1"), assertEnabled(DeprecatedRuntimeMetrics, true))) t.Run("empty", run(assertEnabled(DeprecatedRuntimeMetrics, true))) } func run(steps ...func(*testing.T)) func(*testing.T) { return func(t *testing.T) { t.Helper() for _, step := range steps { step(t) } } } func setenv(k, v string) func(t *testing.T) { //nolint:unparam return func(t *testing.T) { t.Setenv(k, v) } } func assertEnabled(f BoolFeature, enabled bool) func(*testing.T) { return func(t *testing.T) { t.Helper() assert.Equal(t, enabled, f.Enabled(), "not enabled") } } open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/runtime/options.go000066400000000000000000000056171470323427300312320ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package runtime // import "go.opentelemetry.io/contrib/instrumentation/runtime" import ( "time" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/metric" ) // config contains optional settings for reporting runtime metrics. type config struct { // MinimumReadMemStatsInterval sets the minimum interval // between calls to runtime.ReadMemStats(). Negative values // are ignored. MinimumReadMemStatsInterval time.Duration // MeterProvider sets the metric.MeterProvider. If nil, the global // Provider will be used. MeterProvider metric.MeterProvider } // Option supports configuring optional settings for runtime metrics. type Option interface { apply(*config) } // ProducerOption supports configuring optional settings for runtime metrics using a // metric producer in addition to standard instrumentation. type ProducerOption interface { Option applyProducer(*config) } // DefaultMinimumReadMemStatsInterval is the default minimum interval // between calls to runtime.ReadMemStats(). Use the // WithMinimumReadMemStatsInterval() option to modify this setting in // Start(). const DefaultMinimumReadMemStatsInterval time.Duration = 15 * time.Second // WithMinimumReadMemStatsInterval sets a minimum interval between calls to // runtime.ReadMemStats(), which is a relatively expensive call to make // frequently. This setting is ignored when `d` is negative. func WithMinimumReadMemStatsInterval(d time.Duration) Option { return minimumReadMemStatsIntervalOption(d) } type minimumReadMemStatsIntervalOption time.Duration func (o minimumReadMemStatsIntervalOption) apply(c *config) { if o >= 0 { c.MinimumReadMemStatsInterval = time.Duration(o) } } func (o minimumReadMemStatsIntervalOption) applyProducer(c *config) { o.apply(c) } // WithMeterProvider sets the Metric implementation to use for // reporting. If this option is not used, the global metric.MeterProvider // will be used. `provider` must be non-nil. func WithMeterProvider(provider metric.MeterProvider) Option { return metricProviderOption{provider} } type metricProviderOption struct{ metric.MeterProvider } func (o metricProviderOption) apply(c *config) { if o.MeterProvider != nil { c.MeterProvider = o.MeterProvider } } // newConfig computes a config from the supplied Options. func newConfig(opts ...Option) config { c := config{ MeterProvider: otel.GetMeterProvider(), } for _, opt := range opts { opt.apply(&c) } if c.MinimumReadMemStatsInterval <= 0 { c.MinimumReadMemStatsInterval = DefaultMinimumReadMemStatsInterval } return c } // newConfig computes a config from the supplied ProducerOptions. func newProducerConfig(opts ...ProducerOption) config { c := config{} for _, opt := range opts { opt.applyProducer(&c) } if c.MinimumReadMemStatsInterval <= 0 { c.MinimumReadMemStatsInterval = DefaultMinimumReadMemStatsInterval } return c } open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/runtime/options_test.go000066400000000000000000000021321470323427300322560ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package runtime // import "go.opentelemetry.io/contrib/instrumentation/runtime" import ( "testing" "time" "github.com/stretchr/testify/assert" ) func TestNewConfig(t *testing.T) { for _, tt := range []struct { name string opts []Option expect config }{ { name: "default", expect: config{MinimumReadMemStatsInterval: 15 * time.Second}, }, { name: "negative MinimumReadMemStatsInterval ignored", opts: []Option{WithMinimumReadMemStatsInterval(-1 * time.Second)}, expect: config{MinimumReadMemStatsInterval: 15 * time.Second}, }, { name: "set MinimumReadMemStatsInterval", opts: []Option{WithMinimumReadMemStatsInterval(10 * time.Second)}, expect: config{MinimumReadMemStatsInterval: 10 * time.Second}, }, } { t.Run(tt.name, func(t *testing.T) { got := newConfig(tt.opts...) assert.True(t, configEqual(got, tt.expect)) }) } } func configEqual(a, b config) bool { // ignore MeterProvider return a.MinimumReadMemStatsInterval == b.MinimumReadMemStatsInterval } open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/runtime/producer.go000066400000000000000000000065641470323427300313640ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package runtime // import "go.opentelemetry.io/contrib/instrumentation/runtime" import ( "context" "fmt" "math" "runtime/metrics" "sync" "time" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/instrumentation" "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" ) var startTime time.Time func init() { startTime = time.Now() } var histogramMetrics = []string{goSchedLatencies} // Producer is a metric.Producer, which provides precomputed histogram metrics from the go runtime. type Producer struct { lock sync.Mutex collector *goCollector } var _ metric.Producer = (*Producer)(nil) // NewProducer creates a Producer which provides precomputed histogram metrics from the go runtime. func NewProducer(opts ...ProducerOption) *Producer { c := newProducerConfig(opts...) return &Producer{ collector: newCollector(c.MinimumReadMemStatsInterval, histogramMetrics), } } // Produce returns precomputed histogram metrics from the go runtime, or an error if unsuccessful. func (p *Producer) Produce(context.Context) ([]metricdata.ScopeMetrics, error) { p.lock.Lock() p.collector.refresh() schedHist := p.collector.getHistogram(goSchedLatencies) p.lock.Unlock() // Use the last collection time (which may or may not be now) for the timestamp. histDp := convertRuntimeHistogram(schedHist, p.collector.lastCollect) if len(histDp) == 0 { return nil, fmt.Errorf("unable to obtain go.schedule.duration metric from the runtime") } return []metricdata.ScopeMetrics{ { Scope: instrumentation.Scope{ Name: ScopeName, Version: Version(), }, Metrics: []metricdata.Metrics{ { Name: "go.schedule.duration", Description: "The time goroutines have spent in the scheduler in a runnable state before actually running.", Unit: "s", Data: metricdata.Histogram[float64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: histDp, }, }, }, }, }, nil } var emptySet = attribute.EmptySet() func convertRuntimeHistogram(runtimeHist *metrics.Float64Histogram, ts time.Time) []metricdata.HistogramDataPoint[float64] { if runtimeHist == nil { return nil } bounds := runtimeHist.Buckets counts := runtimeHist.Counts if len(bounds) < 2 { // runtime histograms are guaranteed to have at least two bucket boundaries. return nil } // trim the first bucket since it is a lower bound. OTel histogram boundaries only have an upper bound. bounds = bounds[1:] if bounds[len(bounds)-1] == math.Inf(1) { // trim the last bucket if it is +Inf, since the +Inf boundary is implicit in OTel. bounds = bounds[:len(bounds)-1] } else { // if the last bucket is not +Inf, append an extra zero count since // the implicit +Inf bucket won't have any observations. counts = append(counts, 0) } count := uint64(0) sum := float64(0) for i, c := range counts { count += c // This computed sum is an underestimate, since it assumes each // observation happens at the bucket's lower bound. if i > 0 && count != 0 { sum += bounds[i-1] * float64(count) } } return []metricdata.HistogramDataPoint[float64]{ { StartTime: startTime, Count: count, Sum: sum, Time: ts, Bounds: bounds, BucketCounts: counts, Attributes: *emptySet, }, } } open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/runtime/producer_test.go000066400000000000000000000030171470323427300324110ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package runtime // import "go.opentelemetry.io/contrib/instrumentation/runtime" import ( "context" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/sdk/instrumentation" "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest" ) func TestNewProducer(t *testing.T) { reader := metric.NewManualReader(metric.WithProducer(NewProducer())) _ = metric.NewMeterProvider(metric.WithReader(reader)) rm := metricdata.ResourceMetrics{} err := reader.Collect(context.Background(), &rm) assert.NoError(t, err) require.Len(t, rm.ScopeMetrics, 1) require.Len(t, rm.ScopeMetrics[0].Metrics, 1) expectedScopeMetric := metricdata.ScopeMetrics{ Scope: instrumentation.Scope{ Name: "go.opentelemetry.io/contrib/instrumentation/runtime", Version: Version(), }, Metrics: []metricdata.Metrics{ { Name: "go.schedule.duration", Description: "The time goroutines have spent in the scheduler in a runnable state before actually running.", Unit: "s", Data: metricdata.Histogram[float64]{ Temporality: metricdata.CumulativeTemporality, DataPoints: []metricdata.HistogramDataPoint[float64]{ {}, }, }, }, }, } metricdatatest.AssertEqual(t, expectedScopeMetric, rm.ScopeMetrics[0], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue()) } open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/runtime/runtime.go000066400000000000000000000162211470323427300312130ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package runtime // import "go.opentelemetry.io/contrib/instrumentation/runtime" import ( "context" "math" "runtime/metrics" "sync" "time" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/contrib/instrumentation/runtime/internal/deprecatedruntime" "go.opentelemetry.io/contrib/instrumentation/runtime/internal/x" ) // ScopeName is the instrumentation scope name. const ScopeName = "go.opentelemetry.io/contrib/instrumentation/runtime" const ( goTotalMemory = "/memory/classes/total:bytes" goMemoryReleased = "/memory/classes/heap/released:bytes" goHeapMemory = "/memory/classes/heap/stacks:bytes" goMemoryLimit = "/gc/gomemlimit:bytes" goMemoryAllocated = "/gc/heap/allocs:bytes" goMemoryAllocations = "/gc/heap/allocs:objects" goMemoryGoal = "/gc/heap/goal:bytes" goGoroutines = "/sched/goroutines:goroutines" goMaxProcs = "/sched/gomaxprocs:threads" goConfigGC = "/gc/gogc:percent" goSchedLatencies = "/sched/latencies:seconds" ) // Start initializes reporting of runtime metrics using the supplied config. func Start(opts ...Option) error { c := newConfig(opts...) meter := c.MeterProvider.Meter( ScopeName, metric.WithInstrumentationVersion(Version()), ) if x.DeprecatedRuntimeMetrics.Enabled() { return deprecatedruntime.Start(meter, c.MinimumReadMemStatsInterval) } memoryUsedInstrument, err := meter.Int64ObservableUpDownCounter( "go.memory.used", metric.WithUnit("By"), metric.WithDescription("Memory used by the Go runtime."), ) if err != nil { return err } memoryLimitInstrument, err := meter.Int64ObservableUpDownCounter( "go.memory.limit", metric.WithUnit("By"), metric.WithDescription("Go runtime memory limit configured by the user, if a limit exists."), ) if err != nil { return err } memoryAllocatedInstrument, err := meter.Int64ObservableCounter( "go.memory.allocated", metric.WithUnit("By"), metric.WithDescription("Memory allocated to the heap by the application."), ) if err != nil { return err } memoryAllocationsInstrument, err := meter.Int64ObservableCounter( "go.memory.allocations", metric.WithUnit("{allocation}"), metric.WithDescription("Count of allocations to the heap by the application."), ) if err != nil { return err } memoryGCGoalInstrument, err := meter.Int64ObservableUpDownCounter( "go.memory.gc.goal", metric.WithUnit("By"), metric.WithDescription("Heap size target for the end of the GC cycle."), ) if err != nil { return err } goroutineCountInstrument, err := meter.Int64ObservableUpDownCounter( "go.goroutine.count", metric.WithUnit("{goroutine}"), metric.WithDescription("Count of live goroutines."), ) if err != nil { return err } processorLimitInstrument, err := meter.Int64ObservableUpDownCounter( "go.processor.limit", metric.WithUnit("{thread}"), metric.WithDescription("The number of OS threads that can execute user-level Go code simultaneously."), ) if err != nil { return err } gogcConfigInstrument, err := meter.Int64ObservableUpDownCounter( "go.config.gogc", metric.WithUnit("%"), metric.WithDescription("Heap size target percentage configured by the user, otherwise 100."), ) if err != nil { return err } otherMemoryOpt := metric.WithAttributeSet( attribute.NewSet(attribute.String("go.memory.type", "other")), ) stackMemoryOpt := metric.WithAttributeSet( attribute.NewSet(attribute.String("go.memory.type", "stack")), ) collector := newCollector(c.MinimumReadMemStatsInterval, runtimeMetrics) var lock sync.Mutex _, err = meter.RegisterCallback( func(ctx context.Context, o metric.Observer) error { lock.Lock() defer lock.Unlock() collector.refresh() stackMemory := collector.getInt(goHeapMemory) o.ObserveInt64(memoryUsedInstrument, stackMemory, stackMemoryOpt) totalMemory := collector.getInt(goTotalMemory) - collector.getInt(goMemoryReleased) otherMemory := totalMemory - stackMemory o.ObserveInt64(memoryUsedInstrument, otherMemory, otherMemoryOpt) // Only observe the limit metric if a limit exists if limit := collector.getInt(goMemoryLimit); limit != math.MaxInt64 { o.ObserveInt64(memoryLimitInstrument, limit) } o.ObserveInt64(memoryAllocatedInstrument, collector.getInt(goMemoryAllocated)) o.ObserveInt64(memoryAllocationsInstrument, collector.getInt(goMemoryAllocations)) o.ObserveInt64(memoryGCGoalInstrument, collector.getInt(goMemoryGoal)) o.ObserveInt64(goroutineCountInstrument, collector.getInt(goGoroutines)) o.ObserveInt64(processorLimitInstrument, collector.getInt(goMaxProcs)) o.ObserveInt64(gogcConfigInstrument, collector.getInt(goConfigGC)) return nil }, memoryUsedInstrument, memoryLimitInstrument, memoryAllocatedInstrument, memoryAllocationsInstrument, memoryGCGoalInstrument, goroutineCountInstrument, processorLimitInstrument, gogcConfigInstrument, ) if err != nil { return err } return nil } // These are the metrics we actually fetch from the go runtime. var runtimeMetrics = []string{ goTotalMemory, goMemoryReleased, goHeapMemory, goMemoryLimit, goMemoryAllocated, goMemoryAllocations, goMemoryGoal, goGoroutines, goMaxProcs, goConfigGC, } type goCollector struct { // now is used to replace the implementation of time.Now for testing now func() time.Time // lastCollect tracks the last time metrics were refreshed lastCollect time.Time // minimumInterval is the minimum amount of time between calls to metrics.Read minimumInterval time.Duration // sampleBuffer is populated by runtime/metrics sampleBuffer []metrics.Sample // sampleMap allows us to easily get the value of a single metric sampleMap map[string]*metrics.Sample } func newCollector(minimumInterval time.Duration, metricNames []string) *goCollector { g := &goCollector{ sampleBuffer: make([]metrics.Sample, 0, len(metricNames)), sampleMap: make(map[string]*metrics.Sample, len(metricNames)), minimumInterval: minimumInterval, now: time.Now, } for _, metricName := range metricNames { g.sampleBuffer = append(g.sampleBuffer, metrics.Sample{Name: metricName}) // sampleMap references a position in the sampleBuffer slice. If an // element is appended to sampleBuffer, it must be added to sampleMap // for the sample to be accessible in sampleMap. g.sampleMap[metricName] = &g.sampleBuffer[len(g.sampleBuffer)-1] } return g } func (g *goCollector) refresh() { now := g.now() if now.Sub(g.lastCollect) < g.minimumInterval { // refresh was invoked more frequently than allowed by the minimum // interval. Do nothing. return } metrics.Read(g.sampleBuffer) g.lastCollect = now } func (g *goCollector) getInt(name string) int64 { if s, ok := g.sampleMap[name]; ok && s.Value.Kind() == metrics.KindUint64 { v := s.Value.Uint64() if v > math.MaxInt64 { return math.MaxInt64 } return int64(v) // nolint: gosec // Overflow checked above. } return 0 } func (g *goCollector) getHistogram(name string) *metrics.Float64Histogram { if s, ok := g.sampleMap[name]; ok && s.Value.Kind() == metrics.KindFloat64Histogram { return s.Value.Float64Histogram() } return nil } open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/runtime/runtime_test.go000066400000000000000000000136521470323427300322570ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package runtime // import "go.opentelemetry.io/contrib/instrumentation/runtime" import ( "context" "math" "runtime/debug" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/instrumentation" "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest" ) func TestRefreshGoCollector(t *testing.T) { // buffer for allocating memory var buffer [][]byte collector := newCollector(10*time.Second, runtimeMetrics) testClock := newClock() collector.now = testClock.now // before the first refresh, all counters are zero assert.Zero(t, collector.getInt(goMemoryAllocations)) // after the first refresh, counters are non-zero buffer = allocateMemory(buffer) collector.refresh() initialAllocations := collector.getInt(goMemoryAllocations) assert.NotZero(t, initialAllocations) // if less than the refresh time has elapsed, the value is not updated // on refresh. testClock.increment(9 * time.Second) collector.refresh() buffer = allocateMemory(buffer) assert.Equal(t, initialAllocations, collector.getInt(goMemoryAllocations)) // if greater than the refresh time has elapsed, the value changes. testClock.increment(2 * time.Second) collector.refresh() _ = allocateMemory(buffer) assert.NotEqual(t, initialAllocations, collector.getInt(goMemoryAllocations)) } func newClock() *clock { return &clock{current: time.Now()} } type clock struct { current time.Time } func (c *clock) now() time.Time { return c.current } func (c *clock) increment(d time.Duration) { c.current = c.current.Add(d) } func TestRuntimeWithLimit(t *testing.T) { // buffer for allocating memory var buffer [][]byte _ = allocateMemory(buffer) t.Setenv("OTEL_GO_X_DEPRECATED_RUNTIME_METRICS", "false") debug.SetMemoryLimit(1234567890) // reset to default defer debug.SetMemoryLimit(math.MaxInt64) reader := metric.NewManualReader() mp := metric.NewMeterProvider(metric.WithReader(reader)) err := Start(WithMeterProvider(mp)) assert.NoError(t, err) rm := metricdata.ResourceMetrics{} err = reader.Collect(context.Background(), &rm) assert.NoError(t, err) require.Len(t, rm.ScopeMetrics, 1) require.Len(t, rm.ScopeMetrics[0].Metrics, 8) expectedScopeMetric := metricdata.ScopeMetrics{ Scope: instrumentation.Scope{ Name: "go.opentelemetry.io/contrib/instrumentation/runtime", Version: Version(), }, Metrics: []metricdata.Metrics{ { Name: "go.memory.used", Description: "Memory used by the Go runtime.", Unit: "By", Data: metricdata.Sum[int64]{ Temporality: metricdata.CumulativeTemporality, IsMonotonic: false, DataPoints: []metricdata.DataPoint[int64]{ { Attributes: attribute.NewSet(attribute.String("go.memory.type", "stack")), }, { Attributes: attribute.NewSet(attribute.String("go.memory.type", "other")), }, }, }, }, { Name: "go.memory.limit", Description: "Go runtime memory limit configured by the user, if a limit exists.", Unit: "By", Data: metricdata.Sum[int64]{ Temporality: metricdata.CumulativeTemporality, IsMonotonic: false, DataPoints: []metricdata.DataPoint[int64]{{}}, }, }, { Name: "go.memory.allocated", Description: "Memory allocated to the heap by the application.", Unit: "By", Data: metricdata.Sum[int64]{ Temporality: metricdata.CumulativeTemporality, IsMonotonic: true, DataPoints: []metricdata.DataPoint[int64]{{}}, }, }, { Name: "go.memory.allocations", Description: "Count of allocations to the heap by the application.", Unit: "{allocation}", Data: metricdata.Sum[int64]{ Temporality: metricdata.CumulativeTemporality, IsMonotonic: true, DataPoints: []metricdata.DataPoint[int64]{{}}, }, }, { Name: "go.memory.gc.goal", Description: "Heap size target for the end of the GC cycle.", Unit: "By", Data: metricdata.Sum[int64]{ Temporality: metricdata.CumulativeTemporality, IsMonotonic: false, DataPoints: []metricdata.DataPoint[int64]{{}}, }, }, { Name: "go.goroutine.count", Description: "Count of live goroutines.", Unit: "{goroutine}", Data: metricdata.Sum[int64]{ Temporality: metricdata.CumulativeTemporality, IsMonotonic: false, DataPoints: []metricdata.DataPoint[int64]{{}}, }, }, { Name: "go.processor.limit", Description: "The number of OS threads that can execute user-level Go code simultaneously.", Unit: "{thread}", Data: metricdata.Sum[int64]{ Temporality: metricdata.CumulativeTemporality, IsMonotonic: false, DataPoints: []metricdata.DataPoint[int64]{{}}, }, }, { Name: "go.config.gogc", Description: "Heap size target percentage configured by the user, otherwise 100.", Unit: "%", Data: metricdata.Sum[int64]{ Temporality: metricdata.CumulativeTemporality, IsMonotonic: false, DataPoints: []metricdata.DataPoint[int64]{{}}, }, }, }, } metricdatatest.AssertEqual(t, expectedScopeMetric, rm.ScopeMetrics[0], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue()) assertNonZeroValues(t, rm.ScopeMetrics[0]) } func assertNonZeroValues(t *testing.T, sm metricdata.ScopeMetrics) { for _, m := range sm.Metrics { switch a := m.Data.(type) { case metricdata.Sum[int64]: for _, dp := range a.DataPoints { assert.Positivef(t, dp.Value, "Metric %q should have a non-zero value for point with attributes %+v", m.Name, dp.Attributes) } default: t.Fatalf("unexpected data type %v", a) } } } func allocateMemory(buffer [][]byte) [][]byte { return append(buffer, make([]byte, 1000000)) } open-telemetry-opentelemetry-go-contrib-e5abccb/instrumentation/runtime/version.go000066400000000000000000000010041470323427300312060ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package runtime // import "go.opentelemetry.io/contrib/instrumentation/runtime" // Version is the current release version of the runtime instrumentation. func Version() string { return "0.56.0" // This string is updated by the pre_release.sh script during release } // SemVersion is the semantic version to be supplied to tracer/meter creation. // // Deprecated: Use [Version] instead. func SemVersion() string { return Version() } open-telemetry-opentelemetry-go-contrib-e5abccb/internal/000077500000000000000000000000001470323427300240655ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/internal/shared/000077500000000000000000000000001470323427300253335ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/internal/shared/README.md000066400000000000000000000002311470323427300266060ustar00rootroot00000000000000# Shared Code under this directory contains reusable internal code which is distributed across packages using `//go:generate gotmpl` in `gen.go` files. open-telemetry-opentelemetry-go-contrib-e5abccb/internal/shared/semconvutil/000077500000000000000000000000001470323427300277035ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/internal/shared/semconvutil/httpconv.go.tmpl000066400000000000000000000464421470323427300330640ustar00rootroot00000000000000// Code created by gotmpl. DO NOT MODIFY. // source: internal/shared/semconvutil/httpconv.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconvutil import ( "fmt" "net/http" "strings" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" semconv "go.opentelemetry.io/otel/semconv/v1.20.0" ) // HTTPClientResponse returns trace attributes for an HTTP response received by a // client from a server. It will return the following attributes if the related // values are defined in resp: "http.status.code", // "http.response_content_length". // // This does not add all OpenTelemetry required attributes for an HTTP event, // it assumes ClientRequest was used to create the span with a complete set of // attributes. If a complete set of attributes can be generated using the // request contained in resp. For example: // // append(HTTPClientResponse(resp), ClientRequest(resp.Request)...) func HTTPClientResponse(resp *http.Response) []attribute.KeyValue { return hc.ClientResponse(resp) } // HTTPClientRequest returns trace attributes for an HTTP request made by a client. // The following attributes are always returned: "http.url", "http.method", // "net.peer.name". The following attributes are returned if the related values // are defined in req: "net.peer.port", "user_agent.original", // "http.request_content_length". func HTTPClientRequest(req *http.Request) []attribute.KeyValue { return hc.ClientRequest(req) } // HTTPClientRequestMetrics returns metric attributes for an HTTP request made by a client. // The following attributes are always returned: "http.method", "net.peer.name". // The following attributes are returned if the // related values are defined in req: "net.peer.port". func HTTPClientRequestMetrics(req *http.Request) []attribute.KeyValue { return hc.ClientRequestMetrics(req) } // HTTPClientStatus returns a span status code and message for an HTTP status code // value received by a client. func HTTPClientStatus(code int) (codes.Code, string) { return hc.ClientStatus(code) } // HTTPServerRequest returns trace attributes for an HTTP request received by a // server. // // The server must be the primary server name if it is known. For example this // would be the ServerName directive // (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache // server, and the server_name directive // (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an // nginx server. More generically, the primary server name would be the host // header value that matches the default virtual host of an HTTP server. It // should include the host identifier and if a port is used to route to the // server that port identifier should be included as an appropriate port // suffix. // // If the primary server name is not known, server should be an empty string. // The req Host will be used to determine the server instead. // // The following attributes are always returned: "http.method", "http.scheme", // "http.target", "net.host.name". The following attributes are returned if // they related values are defined in req: "net.host.port", "net.sock.peer.addr", // "net.sock.peer.port", "user_agent.original", "http.client_ip". func HTTPServerRequest(server string, req *http.Request) []attribute.KeyValue { return hc.ServerRequest(server, req) } // HTTPServerRequestMetrics returns metric attributes for an HTTP request received by a // server. // // The server must be the primary server name if it is known. For example this // would be the ServerName directive // (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache // server, and the server_name directive // (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an // nginx server. More generically, the primary server name would be the host // header value that matches the default virtual host of an HTTP server. It // should include the host identifier and if a port is used to route to the // server that port identifier should be included as an appropriate port // suffix. // // If the primary server name is not known, server should be an empty string. // The req Host will be used to determine the server instead. // // The following attributes are always returned: "http.method", "http.scheme", // "net.host.name". The following attributes are returned if they related // values are defined in req: "net.host.port". func HTTPServerRequestMetrics(server string, req *http.Request) []attribute.KeyValue { return hc.ServerRequestMetrics(server, req) } // HTTPServerStatus returns a span status code and message for an HTTP status code // value returned by a server. Status codes in the 400-499 range are not // returned as errors. func HTTPServerStatus(code int) (codes.Code, string) { return hc.ServerStatus(code) } // httpConv are the HTTP semantic convention attributes defined for a version // of the OpenTelemetry specification. type httpConv struct { NetConv *netConv HTTPClientIPKey attribute.Key HTTPMethodKey attribute.Key HTTPRequestContentLengthKey attribute.Key HTTPResponseContentLengthKey attribute.Key HTTPRouteKey attribute.Key HTTPSchemeHTTP attribute.KeyValue HTTPSchemeHTTPS attribute.KeyValue HTTPStatusCodeKey attribute.Key HTTPTargetKey attribute.Key HTTPURLKey attribute.Key UserAgentOriginalKey attribute.Key } var hc = &httpConv{ NetConv: nc, HTTPClientIPKey: semconv.HTTPClientIPKey, HTTPMethodKey: semconv.HTTPMethodKey, HTTPRequestContentLengthKey: semconv.HTTPRequestContentLengthKey, HTTPResponseContentLengthKey: semconv.HTTPResponseContentLengthKey, HTTPRouteKey: semconv.HTTPRouteKey, HTTPSchemeHTTP: semconv.HTTPSchemeHTTP, HTTPSchemeHTTPS: semconv.HTTPSchemeHTTPS, HTTPStatusCodeKey: semconv.HTTPStatusCodeKey, HTTPTargetKey: semconv.HTTPTargetKey, HTTPURLKey: semconv.HTTPURLKey, UserAgentOriginalKey: semconv.UserAgentOriginalKey, } // ClientResponse returns attributes for an HTTP response received by a client // from a server. The following attributes are returned if the related values // are defined in resp: "http.status.code", "http.response_content_length". // // This does not add all OpenTelemetry required attributes for an HTTP event, // it assumes ClientRequest was used to create the span with a complete set of // attributes. If a complete set of attributes can be generated using the // request contained in resp. For example: // // append(ClientResponse(resp), ClientRequest(resp.Request)...) func (c *httpConv) ClientResponse(resp *http.Response) []attribute.KeyValue { /* The following semantic conventions are returned if present: http.status_code int http.response_content_length int */ var n int if resp.StatusCode > 0 { n++ } if resp.ContentLength > 0 { n++ } attrs := make([]attribute.KeyValue, 0, n) if resp.StatusCode > 0 { attrs = append(attrs, c.HTTPStatusCodeKey.Int(resp.StatusCode)) } if resp.ContentLength > 0 { attrs = append(attrs, c.HTTPResponseContentLengthKey.Int(int(resp.ContentLength))) } return attrs } // ClientRequest returns attributes for an HTTP request made by a client. The // following attributes are always returned: "http.url", "http.method", // "net.peer.name". The following attributes are returned if the related values // are defined in req: "net.peer.port", "user_agent.original", // "http.request_content_length", "user_agent.original". func (c *httpConv) ClientRequest(req *http.Request) []attribute.KeyValue { /* The following semantic conventions are returned if present: http.method string user_agent.original string http.url string net.peer.name string net.peer.port int http.request_content_length int */ /* The following semantic conventions are not returned: http.status_code This requires the response. See ClientResponse. http.response_content_length This requires the response. See ClientResponse. net.sock.family This requires the socket used. net.sock.peer.addr This requires the socket used. net.sock.peer.name This requires the socket used. net.sock.peer.port This requires the socket used. http.resend_count This is something outside of a single request. net.protocol.name The value is the Request is ignored, and the go client will always use "http". net.protocol.version The value in the Request is ignored, and the go client will always use 1.1 or 2.0. */ n := 3 // URL, peer name, proto, and method. var h string if req.URL != nil { h = req.URL.Host } peer, p := firstHostPort(h, req.Header.Get("Host")) port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", p) if port > 0 { n++ } useragent := req.UserAgent() if useragent != "" { n++ } if req.ContentLength > 0 { n++ } attrs := make([]attribute.KeyValue, 0, n) attrs = append(attrs, c.method(req.Method)) var u string if req.URL != nil { // Remove any username/password info that may be in the URL. userinfo := req.URL.User req.URL.User = nil u = req.URL.String() // Restore any username/password info that was removed. req.URL.User = userinfo } attrs = append(attrs, c.HTTPURLKey.String(u)) attrs = append(attrs, c.NetConv.PeerName(peer)) if port > 0 { attrs = append(attrs, c.NetConv.PeerPort(port)) } if useragent != "" { attrs = append(attrs, c.UserAgentOriginalKey.String(useragent)) } if l := req.ContentLength; l > 0 { attrs = append(attrs, c.HTTPRequestContentLengthKey.Int64(l)) } return attrs } // ClientRequestMetrics returns metric attributes for an HTTP request made by a client. The // following attributes are always returned: "http.method", "net.peer.name". // The following attributes are returned if the related values // are defined in req: "net.peer.port". func (c *httpConv) ClientRequestMetrics(req *http.Request) []attribute.KeyValue { /* The following semantic conventions are returned if present: http.method string net.peer.name string net.peer.port int */ n := 2 // method, peer name. var h string if req.URL != nil { h = req.URL.Host } peer, p := firstHostPort(h, req.Header.Get("Host")) port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", p) if port > 0 { n++ } attrs := make([]attribute.KeyValue, 0, n) attrs = append(attrs, c.method(req.Method), c.NetConv.PeerName(peer)) if port > 0 { attrs = append(attrs, c.NetConv.PeerPort(port)) } return attrs } // ServerRequest returns attributes for an HTTP request received by a server. // // The server must be the primary server name if it is known. For example this // would be the ServerName directive // (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache // server, and the server_name directive // (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an // nginx server. More generically, the primary server name would be the host // header value that matches the default virtual host of an HTTP server. It // should include the host identifier and if a port is used to route to the // server that port identifier should be included as an appropriate port // suffix. // // If the primary server name is not known, server should be an empty string. // The req Host will be used to determine the server instead. // // The following attributes are always returned: "http.method", "http.scheme", // "http.target", "net.host.name". The following attributes are returned if they // related values are defined in req: "net.host.port", "net.sock.peer.addr", // "net.sock.peer.port", "user_agent.original", "http.client_ip", // "net.protocol.name", "net.protocol.version". func (c *httpConv) ServerRequest(server string, req *http.Request) []attribute.KeyValue { /* The following semantic conventions are returned if present: http.method string http.scheme string net.host.name string net.host.port int net.sock.peer.addr string net.sock.peer.port int user_agent.original string http.client_ip string net.protocol.name string Note: not set if the value is "http". net.protocol.version string http.target string Note: doesn't include the query parameter. */ /* The following semantic conventions are not returned: http.status_code This requires the response. http.request_content_length This requires the len() of body, which can mutate it. http.response_content_length This requires the response. http.route This is not available. net.sock.peer.name This would require a DNS lookup. net.sock.host.addr The request doesn't have access to the underlying socket. net.sock.host.port The request doesn't have access to the underlying socket. */ n := 4 // Method, scheme, proto, and host name. var host string var p int if server == "" { host, p = splitHostPort(req.Host) } else { // Prioritize the primary server name. host, p = splitHostPort(server) if p < 0 { _, p = splitHostPort(req.Host) } } hostPort := requiredHTTPPort(req.TLS != nil, p) if hostPort > 0 { n++ } peer, peerPort := splitHostPort(req.RemoteAddr) if peer != "" { n++ if peerPort > 0 { n++ } } useragent := req.UserAgent() if useragent != "" { n++ } clientIP := serverClientIP(req.Header.Get("X-Forwarded-For")) if clientIP != "" { n++ } var target string if req.URL != nil { target = req.URL.Path if target != "" { n++ } } protoName, protoVersion := netProtocol(req.Proto) if protoName != "" && protoName != "http" { n++ } if protoVersion != "" { n++ } attrs := make([]attribute.KeyValue, 0, n) attrs = append(attrs, c.method(req.Method)) attrs = append(attrs, c.scheme(req.TLS != nil)) attrs = append(attrs, c.NetConv.HostName(host)) if hostPort > 0 { attrs = append(attrs, c.NetConv.HostPort(hostPort)) } if peer != "" { // The Go HTTP server sets RemoteAddr to "IP:port", this will not be a // file-path that would be interpreted with a sock family. attrs = append(attrs, c.NetConv.SockPeerAddr(peer)) if peerPort > 0 { attrs = append(attrs, c.NetConv.SockPeerPort(peerPort)) } } if useragent != "" { attrs = append(attrs, c.UserAgentOriginalKey.String(useragent)) } if clientIP != "" { attrs = append(attrs, c.HTTPClientIPKey.String(clientIP)) } if target != "" { attrs = append(attrs, c.HTTPTargetKey.String(target)) } if protoName != "" && protoName != "http" { attrs = append(attrs, c.NetConv.NetProtocolName.String(protoName)) } if protoVersion != "" { attrs = append(attrs, c.NetConv.NetProtocolVersion.String(protoVersion)) } return attrs } // ServerRequestMetrics returns metric attributes for an HTTP request received // by a server. // // The server must be the primary server name if it is known. For example this // would be the ServerName directive // (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache // server, and the server_name directive // (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an // nginx server. More generically, the primary server name would be the host // header value that matches the default virtual host of an HTTP server. It // should include the host identifier and if a port is used to route to the // server that port identifier should be included as an appropriate port // suffix. // // If the primary server name is not known, server should be an empty string. // The req Host will be used to determine the server instead. // // The following attributes are always returned: "http.method", "http.scheme", // "net.host.name". The following attributes are returned if they related // values are defined in req: "net.host.port". func (c *httpConv) ServerRequestMetrics(server string, req *http.Request) []attribute.KeyValue { /* The following semantic conventions are returned if present: http.scheme string http.route string http.method string http.status_code int net.host.name string net.host.port int net.protocol.name string Note: not set if the value is "http". net.protocol.version string */ n := 3 // Method, scheme, and host name. var host string var p int if server == "" { host, p = splitHostPort(req.Host) } else { // Prioritize the primary server name. host, p = splitHostPort(server) if p < 0 { _, p = splitHostPort(req.Host) } } hostPort := requiredHTTPPort(req.TLS != nil, p) if hostPort > 0 { n++ } protoName, protoVersion := netProtocol(req.Proto) if protoName != "" { n++ } if protoVersion != "" { n++ } attrs := make([]attribute.KeyValue, 0, n) attrs = append(attrs, c.methodMetric(req.Method)) attrs = append(attrs, c.scheme(req.TLS != nil)) attrs = append(attrs, c.NetConv.HostName(host)) if hostPort > 0 { attrs = append(attrs, c.NetConv.HostPort(hostPort)) } if protoName != "" { attrs = append(attrs, c.NetConv.NetProtocolName.String(protoName)) } if protoVersion != "" { attrs = append(attrs, c.NetConv.NetProtocolVersion.String(protoVersion)) } return attrs } func (c *httpConv) method(method string) attribute.KeyValue { if method == "" { return c.HTTPMethodKey.String(http.MethodGet) } return c.HTTPMethodKey.String(method) } func (c *httpConv) methodMetric(method string) attribute.KeyValue { method = strings.ToUpper(method) switch method { case http.MethodConnect, http.MethodDelete, http.MethodGet, http.MethodHead, http.MethodOptions, http.MethodPatch, http.MethodPost, http.MethodPut, http.MethodTrace: default: method = "_OTHER" } return c.HTTPMethodKey.String(method) } func (c *httpConv) scheme(https bool) attribute.KeyValue { // nolint:revive if https { return c.HTTPSchemeHTTPS } return c.HTTPSchemeHTTP } func serverClientIP(xForwardedFor string) string { if idx := strings.Index(xForwardedFor, ","); idx >= 0 { xForwardedFor = xForwardedFor[:idx] } return xForwardedFor } func requiredHTTPPort(https bool, port int) int { // nolint:revive if https { if port > 0 && port != 443 { return port } } else { if port > 0 && port != 80 { return port } } return -1 } // Return the request host and port from the first non-empty source. func firstHostPort(source ...string) (host string, port int) { for _, hostport := range source { host, port = splitHostPort(hostport) if host != "" || port > 0 { break } } return } // ClientStatus returns a span status code and message for an HTTP status code // value received by a client. func (c *httpConv) ClientStatus(code int) (codes.Code, string) { if code < 100 || code >= 600 { return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) } if code >= 400 { return codes.Error, "" } return codes.Unset, "" } // ServerStatus returns a span status code and message for an HTTP status code // value returned by a server. Status codes in the 400-499 range are not // returned as errors. func (c *httpConv) ServerStatus(code int) (codes.Code, string) { if code < 100 || code >= 600 { return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) } if code >= 500 { return codes.Error, "" } return codes.Unset, "" } open-telemetry-opentelemetry-go-contrib-e5abccb/internal/shared/semconvutil/httpconv_test.go.tmpl000066400000000000000000000372171470323427300341230ustar00rootroot00000000000000// Code created by gotmpl. DO NOT MODIFY. // source: internal/shared/semconvutil/httpconv_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconvutil import ( "net/http" "net/http/httptest" "net/url" "strconv" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" ) func TestHTTPClientResponse(t *testing.T) { const stat, n = 201, 397 resp := &http.Response{ StatusCode: stat, ContentLength: n, } got := HTTPClientResponse(resp) assert.Equal(t, 2, cap(got), "slice capacity") assert.ElementsMatch(t, []attribute.KeyValue{ attribute.Key("http.status_code").Int(stat), attribute.Key("http.response_content_length").Int(n), }, got) } func TestHTTPSClientRequest(t *testing.T) { req := &http.Request{ Method: http.MethodGet, URL: &url.URL{ Scheme: "https", Host: "127.0.0.1:443", Path: "/resource", }, Proto: "HTTP/1.0", ProtoMajor: 1, ProtoMinor: 0, } assert.ElementsMatch( t, []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.url", "https://127.0.0.1:443/resource"), attribute.String("net.peer.name", "127.0.0.1"), }, HTTPClientRequest(req), ) } func TestHTTPSClientRequestMetrics(t *testing.T) { req := &http.Request{ Method: http.MethodGet, URL: &url.URL{ Scheme: "https", Host: "127.0.0.1:443", Path: "/resource", }, Proto: "HTTP/1.0", ProtoMajor: 1, ProtoMinor: 0, } assert.ElementsMatch( t, []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("net.peer.name", "127.0.0.1"), }, HTTPClientRequestMetrics(req), ) } func TestHTTPClientRequest(t *testing.T) { const ( user = "alice" n = 128 agent = "Go-http-client/1.1" ) req := &http.Request{ Method: http.MethodGet, URL: &url.URL{ Scheme: "http", Host: "127.0.0.1:8080", Path: "/resource", }, Proto: "HTTP/1.0", ProtoMajor: 1, ProtoMinor: 0, Header: http.Header{ "User-Agent": []string{agent}, }, ContentLength: n, } req.SetBasicAuth(user, "pswrd") assert.ElementsMatch( t, []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.url", "http://127.0.0.1:8080/resource"), attribute.String("net.peer.name", "127.0.0.1"), attribute.Int("net.peer.port", 8080), attribute.String("user_agent.original", agent), attribute.Int("http.request_content_length", n), }, HTTPClientRequest(req), ) } func TestHTTPClientRequestMetrics(t *testing.T) { const ( user = "alice" n = 128 agent = "Go-http-client/1.1" ) req := &http.Request{ Method: http.MethodGet, URL: &url.URL{ Scheme: "http", Host: "127.0.0.1:8080", Path: "/resource", }, Proto: "HTTP/1.0", ProtoMajor: 1, ProtoMinor: 0, Header: http.Header{ "User-Agent": []string{agent}, }, ContentLength: n, } req.SetBasicAuth(user, "pswrd") assert.ElementsMatch( t, []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("net.peer.name", "127.0.0.1"), attribute.Int("net.peer.port", 8080), }, HTTPClientRequestMetrics(req), ) } func TestHTTPClientRequestRequired(t *testing.T) { req := new(http.Request) var got []attribute.KeyValue assert.NotPanics(t, func() { got = HTTPClientRequest(req) }) want := []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.url", ""), attribute.String("net.peer.name", ""), } assert.Equal(t, want, got) } func TestHTTPServerRequest(t *testing.T) { got := make(chan *http.Request, 1) handler := func(w http.ResponseWriter, r *http.Request) { got <- r w.WriteHeader(http.StatusOK) } srv := httptest.NewServer(http.HandlerFunc(handler)) defer srv.Close() srvURL, err := url.Parse(srv.URL) require.NoError(t, err) srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) require.NoError(t, err) resp, err := srv.Client().Get(srv.URL) require.NoError(t, err) require.NoError(t, resp.Body.Close()) req := <-got peer, peerPort := splitHostPort(req.RemoteAddr) const user = "alice" req.SetBasicAuth(user, "pswrd") const clientIP = "127.0.0.5" req.Header.Add("X-Forwarded-For", clientIP) assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.scheme", "http"), attribute.String("net.host.name", srvURL.Hostname()), attribute.Int("net.host.port", int(srvPort)), attribute.String("net.sock.peer.addr", peer), attribute.Int("net.sock.peer.port", peerPort), attribute.String("user_agent.original", "Go-http-client/1.1"), attribute.String("http.client_ip", clientIP), attribute.String("net.protocol.version", "1.1"), attribute.String("http.target", "/"), }, HTTPServerRequest("", req)) } func TestHTTPServerRequestMetrics(t *testing.T) { got := make(chan *http.Request, 1) handler := func(w http.ResponseWriter, r *http.Request) { got <- r w.WriteHeader(http.StatusOK) } srv := httptest.NewServer(http.HandlerFunc(handler)) defer srv.Close() srvURL, err := url.Parse(srv.URL) require.NoError(t, err) srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) require.NoError(t, err) resp, err := srv.Client().Get(srv.URL) require.NoError(t, err) require.NoError(t, resp.Body.Close()) req := <-got assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.scheme", "http"), attribute.String("net.host.name", srvURL.Hostname()), attribute.Int("net.host.port", int(srvPort)), attribute.String("net.protocol.name", "http"), attribute.String("net.protocol.version", "1.1"), }, HTTPServerRequestMetrics("", req)) } func TestHTTPServerName(t *testing.T) { req := new(http.Request) var got []attribute.KeyValue const ( host = "test.semconv.server" port = 8080 ) portStr := strconv.Itoa(port) server := host + ":" + portStr assert.NotPanics(t, func() { got = HTTPServerRequest(server, req) }) assert.Contains(t, got, attribute.String("net.host.name", host)) assert.Contains(t, got, attribute.Int("net.host.port", port)) req = &http.Request{Host: "alt.host.name:" + portStr} // The server parameter does not include a port, ServerRequest should use // the port in the request Host field. assert.NotPanics(t, func() { got = HTTPServerRequest(host, req) }) assert.Contains(t, got, attribute.String("net.host.name", host)) assert.Contains(t, got, attribute.Int("net.host.port", port)) } func TestHTTPServerRequestFailsGracefully(t *testing.T) { req := new(http.Request) var got []attribute.KeyValue assert.NotPanics(t, func() { got = HTTPServerRequest("", req) }) want := []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.scheme", "http"), attribute.String("net.host.name", ""), } assert.ElementsMatch(t, want, got) } func TestHTTPMethod(t *testing.T) { assert.Equal(t, attribute.String("http.method", "POST"), hc.method("POST")) assert.Equal(t, attribute.String("http.method", "GET"), hc.method("")) assert.Equal(t, attribute.String("http.method", "garbage"), hc.method("garbage")) } func TestHTTPScheme(t *testing.T) { assert.Equal(t, attribute.String("http.scheme", "http"), hc.scheme(false)) assert.Equal(t, attribute.String("http.scheme", "https"), hc.scheme(true)) } func TestHTTPServerClientIP(t *testing.T) { tests := []struct { xForwardedFor string want string }{ {"", ""}, {"127.0.0.1", "127.0.0.1"}, {"127.0.0.1,127.0.0.5", "127.0.0.1"}, } for _, test := range tests { got := serverClientIP(test.xForwardedFor) assert.Equal(t, test.want, got, test.xForwardedFor) } } func TestRequiredHTTPPort(t *testing.T) { tests := []struct { https bool port int want int }{ {true, 443, -1}, {true, 80, 80}, {true, 8081, 8081}, {false, 443, 443}, {false, 80, -1}, {false, 8080, 8080}, } for _, test := range tests { got := requiredHTTPPort(test.https, test.port) assert.Equal(t, test.want, got, test.https, test.port) } } func TestFirstHostPort(t *testing.T) { host, port := "127.0.0.1", 8080 hostport := "127.0.0.1:8080" sources := [][]string{ {hostport}, {"", hostport}, {"", "", hostport}, {"", "", hostport, ""}, {"", "", hostport, "127.0.0.3:80"}, } for _, src := range sources { h, p := firstHostPort(src...) assert.Equal(t, host, h, src) assert.Equal(t, port, p, src) } } func TestHTTPClientStatus(t *testing.T) { tests := []struct { code int stat codes.Code msg bool }{ {0, codes.Error, true}, {http.StatusContinue, codes.Unset, false}, {http.StatusSwitchingProtocols, codes.Unset, false}, {http.StatusProcessing, codes.Unset, false}, {http.StatusEarlyHints, codes.Unset, false}, {http.StatusOK, codes.Unset, false}, {http.StatusCreated, codes.Unset, false}, {http.StatusAccepted, codes.Unset, false}, {http.StatusNonAuthoritativeInfo, codes.Unset, false}, {http.StatusNoContent, codes.Unset, false}, {http.StatusResetContent, codes.Unset, false}, {http.StatusPartialContent, codes.Unset, false}, {http.StatusMultiStatus, codes.Unset, false}, {http.StatusAlreadyReported, codes.Unset, false}, {http.StatusIMUsed, codes.Unset, false}, {http.StatusMultipleChoices, codes.Unset, false}, {http.StatusMovedPermanently, codes.Unset, false}, {http.StatusFound, codes.Unset, false}, {http.StatusSeeOther, codes.Unset, false}, {http.StatusNotModified, codes.Unset, false}, {http.StatusUseProxy, codes.Unset, false}, {306, codes.Unset, false}, {http.StatusTemporaryRedirect, codes.Unset, false}, {http.StatusPermanentRedirect, codes.Unset, false}, {http.StatusBadRequest, codes.Error, false}, {http.StatusUnauthorized, codes.Error, false}, {http.StatusPaymentRequired, codes.Error, false}, {http.StatusForbidden, codes.Error, false}, {http.StatusNotFound, codes.Error, false}, {http.StatusMethodNotAllowed, codes.Error, false}, {http.StatusNotAcceptable, codes.Error, false}, {http.StatusProxyAuthRequired, codes.Error, false}, {http.StatusRequestTimeout, codes.Error, false}, {http.StatusConflict, codes.Error, false}, {http.StatusGone, codes.Error, false}, {http.StatusLengthRequired, codes.Error, false}, {http.StatusPreconditionFailed, codes.Error, false}, {http.StatusRequestEntityTooLarge, codes.Error, false}, {http.StatusRequestURITooLong, codes.Error, false}, {http.StatusUnsupportedMediaType, codes.Error, false}, {http.StatusRequestedRangeNotSatisfiable, codes.Error, false}, {http.StatusExpectationFailed, codes.Error, false}, {http.StatusTeapot, codes.Error, false}, {http.StatusMisdirectedRequest, codes.Error, false}, {http.StatusUnprocessableEntity, codes.Error, false}, {http.StatusLocked, codes.Error, false}, {http.StatusFailedDependency, codes.Error, false}, {http.StatusTooEarly, codes.Error, false}, {http.StatusUpgradeRequired, codes.Error, false}, {http.StatusPreconditionRequired, codes.Error, false}, {http.StatusTooManyRequests, codes.Error, false}, {http.StatusRequestHeaderFieldsTooLarge, codes.Error, false}, {http.StatusUnavailableForLegalReasons, codes.Error, false}, {499, codes.Error, false}, {http.StatusInternalServerError, codes.Error, false}, {http.StatusNotImplemented, codes.Error, false}, {http.StatusBadGateway, codes.Error, false}, {http.StatusServiceUnavailable, codes.Error, false}, {http.StatusGatewayTimeout, codes.Error, false}, {http.StatusHTTPVersionNotSupported, codes.Error, false}, {http.StatusVariantAlsoNegotiates, codes.Error, false}, {http.StatusInsufficientStorage, codes.Error, false}, {http.StatusLoopDetected, codes.Error, false}, {http.StatusNotExtended, codes.Error, false}, {http.StatusNetworkAuthenticationRequired, codes.Error, false}, {600, codes.Error, true}, } for _, test := range tests { t.Run(strconv.Itoa(test.code), func(t *testing.T) { c, msg := HTTPClientStatus(test.code) assert.Equal(t, test.stat, c) if test.msg && msg == "" { t.Errorf("expected non-empty message for %d", test.code) } else if !test.msg && msg != "" { t.Errorf("expected empty message for %d, got: %s", test.code, msg) } }) } } func TestHTTPServerStatus(t *testing.T) { tests := []struct { code int stat codes.Code msg bool }{ {0, codes.Error, true}, {http.StatusContinue, codes.Unset, false}, {http.StatusSwitchingProtocols, codes.Unset, false}, {http.StatusProcessing, codes.Unset, false}, {http.StatusEarlyHints, codes.Unset, false}, {http.StatusOK, codes.Unset, false}, {http.StatusCreated, codes.Unset, false}, {http.StatusAccepted, codes.Unset, false}, {http.StatusNonAuthoritativeInfo, codes.Unset, false}, {http.StatusNoContent, codes.Unset, false}, {http.StatusResetContent, codes.Unset, false}, {http.StatusPartialContent, codes.Unset, false}, {http.StatusMultiStatus, codes.Unset, false}, {http.StatusAlreadyReported, codes.Unset, false}, {http.StatusIMUsed, codes.Unset, false}, {http.StatusMultipleChoices, codes.Unset, false}, {http.StatusMovedPermanently, codes.Unset, false}, {http.StatusFound, codes.Unset, false}, {http.StatusSeeOther, codes.Unset, false}, {http.StatusNotModified, codes.Unset, false}, {http.StatusUseProxy, codes.Unset, false}, {306, codes.Unset, false}, {http.StatusTemporaryRedirect, codes.Unset, false}, {http.StatusPermanentRedirect, codes.Unset, false}, {http.StatusBadRequest, codes.Unset, false}, {http.StatusUnauthorized, codes.Unset, false}, {http.StatusPaymentRequired, codes.Unset, false}, {http.StatusForbidden, codes.Unset, false}, {http.StatusNotFound, codes.Unset, false}, {http.StatusMethodNotAllowed, codes.Unset, false}, {http.StatusNotAcceptable, codes.Unset, false}, {http.StatusProxyAuthRequired, codes.Unset, false}, {http.StatusRequestTimeout, codes.Unset, false}, {http.StatusConflict, codes.Unset, false}, {http.StatusGone, codes.Unset, false}, {http.StatusLengthRequired, codes.Unset, false}, {http.StatusPreconditionFailed, codes.Unset, false}, {http.StatusRequestEntityTooLarge, codes.Unset, false}, {http.StatusRequestURITooLong, codes.Unset, false}, {http.StatusUnsupportedMediaType, codes.Unset, false}, {http.StatusRequestedRangeNotSatisfiable, codes.Unset, false}, {http.StatusExpectationFailed, codes.Unset, false}, {http.StatusTeapot, codes.Unset, false}, {http.StatusMisdirectedRequest, codes.Unset, false}, {http.StatusUnprocessableEntity, codes.Unset, false}, {http.StatusLocked, codes.Unset, false}, {http.StatusFailedDependency, codes.Unset, false}, {http.StatusTooEarly, codes.Unset, false}, {http.StatusUpgradeRequired, codes.Unset, false}, {http.StatusPreconditionRequired, codes.Unset, false}, {http.StatusTooManyRequests, codes.Unset, false}, {http.StatusRequestHeaderFieldsTooLarge, codes.Unset, false}, {http.StatusUnavailableForLegalReasons, codes.Unset, false}, {499, codes.Unset, false}, {http.StatusInternalServerError, codes.Error, false}, {http.StatusNotImplemented, codes.Error, false}, {http.StatusBadGateway, codes.Error, false}, {http.StatusServiceUnavailable, codes.Error, false}, {http.StatusGatewayTimeout, codes.Error, false}, {http.StatusHTTPVersionNotSupported, codes.Error, false}, {http.StatusVariantAlsoNegotiates, codes.Error, false}, {http.StatusInsufficientStorage, codes.Error, false}, {http.StatusLoopDetected, codes.Error, false}, {http.StatusNotExtended, codes.Error, false}, {http.StatusNetworkAuthenticationRequired, codes.Error, false}, {600, codes.Error, true}, } for _, test := range tests { c, msg := HTTPServerStatus(test.code) assert.Equal(t, test.stat, c) if test.msg && msg == "" { t.Errorf("expected non-empty message for %d", test.code) } else if !test.msg && msg != "" { t.Errorf("expected empty message for %d, got: %s", test.code, msg) } } } open-telemetry-opentelemetry-go-contrib-e5abccb/internal/shared/semconvutil/netconv.go.tmpl000066400000000000000000000122141470323427300326610ustar00rootroot00000000000000// Code created by gotmpl. DO NOT MODIFY. // source: internal/shared/semconvutil/netconv.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconvutil // import "go.opentelemetry.io/contrib/internal/shared/semconvutil" import ( "net" "strconv" "strings" "go.opentelemetry.io/otel/attribute" semconv "go.opentelemetry.io/otel/semconv/v1.20.0" ) // NetTransport returns a trace attribute describing the transport protocol of the // passed network. See the net.Dial for information about acceptable network // values. func NetTransport(network string) attribute.KeyValue { return nc.Transport(network) } // netConv are the network semantic convention attributes defined for a version // of the OpenTelemetry specification. type netConv struct { NetHostNameKey attribute.Key NetHostPortKey attribute.Key NetPeerNameKey attribute.Key NetPeerPortKey attribute.Key NetProtocolName attribute.Key NetProtocolVersion attribute.Key NetSockFamilyKey attribute.Key NetSockPeerAddrKey attribute.Key NetSockPeerPortKey attribute.Key NetSockHostAddrKey attribute.Key NetSockHostPortKey attribute.Key NetTransportOther attribute.KeyValue NetTransportTCP attribute.KeyValue NetTransportUDP attribute.KeyValue NetTransportInProc attribute.KeyValue } var nc = &netConv{ NetHostNameKey: semconv.NetHostNameKey, NetHostPortKey: semconv.NetHostPortKey, NetPeerNameKey: semconv.NetPeerNameKey, NetPeerPortKey: semconv.NetPeerPortKey, NetProtocolName: semconv.NetProtocolNameKey, NetProtocolVersion: semconv.NetProtocolVersionKey, NetSockFamilyKey: semconv.NetSockFamilyKey, NetSockPeerAddrKey: semconv.NetSockPeerAddrKey, NetSockPeerPortKey: semconv.NetSockPeerPortKey, NetSockHostAddrKey: semconv.NetSockHostAddrKey, NetSockHostPortKey: semconv.NetSockHostPortKey, NetTransportOther: semconv.NetTransportOther, NetTransportTCP: semconv.NetTransportTCP, NetTransportUDP: semconv.NetTransportUDP, NetTransportInProc: semconv.NetTransportInProc, } func (c *netConv) Transport(network string) attribute.KeyValue { switch network { case "tcp", "tcp4", "tcp6": return c.NetTransportTCP case "udp", "udp4", "udp6": return c.NetTransportUDP case "unix", "unixgram", "unixpacket": return c.NetTransportInProc default: // "ip:*", "ip4:*", and "ip6:*" all are considered other. return c.NetTransportOther } } // Host returns attributes for a network host address. func (c *netConv) Host(address string) []attribute.KeyValue { h, p := splitHostPort(address) var n int if h != "" { n++ if p > 0 { n++ } } if n == 0 { return nil } attrs := make([]attribute.KeyValue, 0, n) attrs = append(attrs, c.HostName(h)) if p > 0 { attrs = append(attrs, c.HostPort(p)) } return attrs } func (c *netConv) HostName(name string) attribute.KeyValue { return c.NetHostNameKey.String(name) } func (c *netConv) HostPort(port int) attribute.KeyValue { return c.NetHostPortKey.Int(port) } func family(network, address string) string { switch network { case "unix", "unixgram", "unixpacket": return "unix" default: if ip := net.ParseIP(address); ip != nil { if ip.To4() == nil { return "inet6" } return "inet" } } return "" } // Peer returns attributes for a network peer address. func (c *netConv) Peer(address string) []attribute.KeyValue { h, p := splitHostPort(address) var n int if h != "" { n++ if p > 0 { n++ } } if n == 0 { return nil } attrs := make([]attribute.KeyValue, 0, n) attrs = append(attrs, c.PeerName(h)) if p > 0 { attrs = append(attrs, c.PeerPort(p)) } return attrs } func (c *netConv) PeerName(name string) attribute.KeyValue { return c.NetPeerNameKey.String(name) } func (c *netConv) PeerPort(port int) attribute.KeyValue { return c.NetPeerPortKey.Int(port) } func (c *netConv) SockPeerAddr(addr string) attribute.KeyValue { return c.NetSockPeerAddrKey.String(addr) } func (c *netConv) SockPeerPort(port int) attribute.KeyValue { return c.NetSockPeerPortKey.Int(port) } // splitHostPort splits a network address hostport of the form "host", // "host%zone", "[host]", "[host%zone], "host:port", "host%zone:port", // "[host]:port", "[host%zone]:port", or ":port" into host or host%zone and // port. // // An empty host is returned if it is not provided or unparsable. A negative // port is returned if it is not provided or unparsable. func splitHostPort(hostport string) (host string, port int) { port = -1 if strings.HasPrefix(hostport, "[") { addrEnd := strings.LastIndex(hostport, "]") if addrEnd < 0 { // Invalid hostport. return } if i := strings.LastIndex(hostport[addrEnd:], ":"); i < 0 { host = hostport[1:addrEnd] return } } else { if i := strings.LastIndex(hostport, ":"); i < 0 { host = hostport return } } host, pStr, err := net.SplitHostPort(hostport) if err != nil { return } p, err := strconv.ParseUint(pStr, 10, 16) if err != nil { return } return host, int(p) // nolint: gosec // Bitsize checked to be 16 above. } func netProtocol(proto string) (name string, version string) { name, version, _ = strings.Cut(proto, "/") name = strings.ToLower(name) return name, version } open-telemetry-opentelemetry-go-contrib-e5abccb/internal/shared/semconvutil/netconv_test.go.tmpl000066400000000000000000000130111470323427300337140ustar00rootroot00000000000000// Code created by gotmpl. DO NOT MODIFY. // source: internal/shared/semconvutil/netconv_test.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package semconvutil import ( "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/attribute" ) const ( addr = "127.0.0.1" port = 1834 ) func TestNetTransport(t *testing.T) { transports := map[string]attribute.KeyValue{ "tcp": attribute.String("net.transport", "ip_tcp"), "tcp4": attribute.String("net.transport", "ip_tcp"), "tcp6": attribute.String("net.transport", "ip_tcp"), "udp": attribute.String("net.transport", "ip_udp"), "udp4": attribute.String("net.transport", "ip_udp"), "udp6": attribute.String("net.transport", "ip_udp"), "unix": attribute.String("net.transport", "inproc"), "unixgram": attribute.String("net.transport", "inproc"), "unixpacket": attribute.String("net.transport", "inproc"), "ip:1": attribute.String("net.transport", "other"), "ip:icmp": attribute.String("net.transport", "other"), "ip4:proto": attribute.String("net.transport", "other"), "ip6:proto": attribute.String("net.transport", "other"), } for network, want := range transports { assert.Equal(t, want, NetTransport(network)) } } func TestNetHost(t *testing.T) { testAddrs(t, []addrTest{ {address: "", expected: nil}, {address: "192.0.0.1", expected: []attribute.KeyValue{ nc.HostName("192.0.0.1"), }}, {address: "192.0.0.1:9090", expected: []attribute.KeyValue{ nc.HostName("192.0.0.1"), nc.HostPort(9090), }}, }, nc.Host) } func TestNetHostName(t *testing.T) { expected := attribute.Key("net.host.name").String(addr) assert.Equal(t, expected, nc.HostName(addr)) } func TestNetHostPort(t *testing.T) { expected := attribute.Key("net.host.port").Int(port) assert.Equal(t, expected, nc.HostPort(port)) } func TestNetPeer(t *testing.T) { testAddrs(t, []addrTest{ {address: "", expected: nil}, {address: "example.com", expected: []attribute.KeyValue{ nc.PeerName("example.com"), }}, {address: "/tmp/file", expected: []attribute.KeyValue{ nc.PeerName("/tmp/file"), }}, {address: "192.0.0.1", expected: []attribute.KeyValue{ nc.PeerName("192.0.0.1"), }}, {address: ":9090", expected: nil}, {address: "192.0.0.1:9090", expected: []attribute.KeyValue{ nc.PeerName("192.0.0.1"), nc.PeerPort(9090), }}, }, nc.Peer) } func TestNetPeerName(t *testing.T) { expected := attribute.Key("net.peer.name").String(addr) assert.Equal(t, expected, nc.PeerName(addr)) } func TestNetPeerPort(t *testing.T) { expected := attribute.Key("net.peer.port").Int(port) assert.Equal(t, expected, nc.PeerPort(port)) } func TestNetSockPeerName(t *testing.T) { expected := attribute.Key("net.sock.peer.addr").String(addr) assert.Equal(t, expected, nc.SockPeerAddr(addr)) } func TestNetSockPeerPort(t *testing.T) { expected := attribute.Key("net.sock.peer.port").Int(port) assert.Equal(t, expected, nc.SockPeerPort(port)) } func TestNetFamily(t *testing.T) { tests := []struct { network string address string expect string }{ {"", "", ""}, {"unix", "", "unix"}, {"unix", "gibberish", "unix"}, {"unixgram", "", "unix"}, {"unixgram", "gibberish", "unix"}, {"unixpacket", "gibberish", "unix"}, {"tcp", "123.0.2.8", "inet"}, {"tcp", "gibberish", ""}, {"", "123.0.2.8", "inet"}, {"", "gibberish", ""}, {"tcp", "fe80::1", "inet6"}, {"", "fe80::1", "inet6"}, } for _, test := range tests { got := family(test.network, test.address) assert.Equal(t, test.expect, got, test.network+"/"+test.address) } } func TestSplitHostPort(t *testing.T) { tests := []struct { hostport string host string port int }{ {"", "", -1}, {":8080", "", 8080}, {"127.0.0.1", "127.0.0.1", -1}, {"www.example.com", "www.example.com", -1}, {"127.0.0.1%25en0", "127.0.0.1%25en0", -1}, {"[]", "", -1}, // Ensure this doesn't panic. {"[fe80::1", "", -1}, {"[fe80::1]", "fe80::1", -1}, {"[fe80::1%25en0]", "fe80::1%25en0", -1}, {"[fe80::1]:8080", "fe80::1", 8080}, {"[fe80::1]::", "", -1}, // Too many colons. {"127.0.0.1:", "127.0.0.1", -1}, {"127.0.0.1:port", "127.0.0.1", -1}, {"127.0.0.1:8080", "127.0.0.1", 8080}, {"www.example.com:8080", "www.example.com", 8080}, {"127.0.0.1%25en0:8080", "127.0.0.1%25en0", 8080}, } for _, test := range tests { h, p := splitHostPort(test.hostport) assert.Equal(t, test.host, h, test.hostport) assert.Equal(t, test.port, p, test.hostport) } } type addrTest struct { address string expected []attribute.KeyValue } func testAddrs(t *testing.T, tests []addrTest, f func(string) []attribute.KeyValue) { t.Helper() for _, test := range tests { got := f(test.address) assert.Equal(t, cap(test.expected), cap(got), "slice capacity") assert.ElementsMatch(t, test.expected, got, test.address) } } func TestNetProtocol(t *testing.T) { type testCase struct { name, version string } tests := map[string]testCase{ "HTTP/1.0": {name: "http", version: "1.0"}, "HTTP/1.1": {name: "http", version: "1.1"}, "HTTP/2": {name: "http", version: "2"}, "HTTP/3": {name: "http", version: "3"}, "SPDY": {name: "spdy"}, "SPDY/2": {name: "spdy", version: "2"}, "QUIC": {name: "quic"}, "unknown/proto/2": {name: "unknown", version: "proto/2"}, "other": {name: "other"}, } for proto, want := range tests { name, version := netProtocol(proto) assert.Equal(t, want.name, name) assert.Equal(t, want.version, version) } } open-telemetry-opentelemetry-go-contrib-e5abccb/lychee.toml000066400000000000000000000002101470323427300244100ustar00rootroot00000000000000exclude_path = [ "zpages/internal/templates/summary.html" # This template's URLs are only expected to be valid on the compiled file ] open-telemetry-opentelemetry-go-contrib-e5abccb/processors/000077500000000000000000000000001470323427300244535ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/processors/baggagecopy/000077500000000000000000000000001470323427300267235ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/processors/baggagecopy/doc.go000066400000000000000000000022471470323427300300240ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package baggagecopy is an OpenTelemetry [Span Processor] that reads key/values // stored in [Baggage] in the starting span's parent context and adds them as // attributes to the span. // // Keys and values added to Baggage will appear on all subsequent child spans for // a trace within this service and will be propagated to external services via // propagation headers. // If the external services also have a Baggage span processor, the keys and // values will appear in those child spans as well. // // Do not put sensitive information in Baggage. // // # Usage // // Add the span processor when configuring the tracer provider. // // The convenience function [AllowAllBaggageKeys] is provided to // allow all baggage keys to be copied to the span. Alternatively, you can // provide a custom baggage key predicate to select which baggage keys you want // to copy. // // [Span Processor]: https://opentelemetry.io/docs/specs/otel/trace/sdk/#span-processor // [Baggage]: https://opentelemetry.io/docs/specs/otel/api/baggage package baggagecopy // import "go.opentelemetry.io/contrib/processors/baggagecopy" open-telemetry-opentelemetry-go-contrib-e5abccb/processors/baggagecopy/example_test.go000066400000000000000000000016021470323427300317430ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package baggagecopy_test import ( "regexp" "strings" "go.opentelemetry.io/contrib/processors/baggagecopy" "go.opentelemetry.io/otel/baggage" "go.opentelemetry.io/otel/sdk/trace" ) func ExampleNew_allKeys() { trace.NewTracerProvider( trace.WithSpanProcessor(baggagecopy.NewSpanProcessor(baggagecopy.AllowAllMembers)), ) } func ExampleNew_keysWithPrefix() { trace.NewTracerProvider( trace.WithSpanProcessor( baggagecopy.NewSpanProcessor( func(m baggage.Member) bool { return strings.HasPrefix(m.Key(), "my-key") }, ), ), ) } func ExampleNew_keysMatchingRegex() { expr := regexp.MustCompile(`^key.+`) trace.NewTracerProvider( trace.WithSpanProcessor( baggagecopy.NewSpanProcessor( func(m baggage.Member) bool { return expr.MatchString(m.Key()) }, ), ), ) } open-telemetry-opentelemetry-go-contrib-e5abccb/processors/baggagecopy/go.mod000066400000000000000000000011411470323427300300260ustar00rootroot00000000000000module go.opentelemetry.io/contrib/processors/baggagecopy go 1.22 require ( github.com/stretchr/testify v1.9.0 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/sdk v1.31.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect go.opentelemetry.io/otel/trace v1.31.0 // indirect golang.org/x/sys v0.26.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) open-telemetry-opentelemetry-go-contrib-e5abccb/processors/baggagecopy/go.sum000066400000000000000000000046721470323427300300670ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= open-telemetry-opentelemetry-go-contrib-e5abccb/processors/baggagecopy/processor.go000066400000000000000000000041361470323427300312750ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package baggagecopy // import "go.opentelemetry.io/contrib/processors/baggagecopy" import ( "context" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/baggage" "go.opentelemetry.io/otel/sdk/trace" ) // Filter returns true if the baggage member should be added to a span. type Filter func(member baggage.Member) bool // AllowAllMembers allows all baggage members to be added to a span. var AllowAllMembers Filter = func(baggage.Member) bool { return true } // SpanProcessor is a [trace.SpanProcessor] implementation that adds baggage // members onto a span as attributes. type SpanProcessor struct { filter Filter } var _ trace.SpanProcessor = (*SpanProcessor)(nil) // NewSpanProcessor returns a new [SpanProcessor]. // // The Baggage span processor duplicates onto a span the attributes found // in Baggage in the parent context at the moment the span is started. // The passed filter determines which baggage members are added to the span. // // If filter is nil, all baggage members will be added. func NewSpanProcessor(filter Filter) *SpanProcessor { return &SpanProcessor{ filter: filter, } } // OnStart is called when a span is started and adds span attributes for baggage contents. func (processor SpanProcessor) OnStart(ctx context.Context, span trace.ReadWriteSpan) { filter := processor.filter if filter == nil { filter = AllowAllMembers } for _, member := range baggage.FromContext(ctx).Members() { if filter(member) { span.SetAttributes(attribute.String(member.Key(), member.Value())) } } } // OnEnd is called when span is finished and is a no-op for this processor. func (processor SpanProcessor) OnEnd(s trace.ReadOnlySpan) {} // Shutdown is called when the SDK shuts down and is a no-op for this processor. func (processor SpanProcessor) Shutdown(context.Context) error { return nil } // ForceFlush exports all ended spans to the configured Exporter that have not yet // been exported and is a no-op for this processor. func (processor SpanProcessor) ForceFlush(context.Context) error { return nil } open-telemetry-opentelemetry-go-contrib-e5abccb/processors/baggagecopy/processor_test.go000066400000000000000000000122751470323427300323370ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package baggagecopy import ( "context" "regexp" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/baggage" "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/sdk/trace/tracetest" ) var _ trace.SpanExporter = &testExporter{} type testExporter struct { spans []trace.ReadOnlySpan } func (e *testExporter) Start(ctx context.Context) error { return nil } func (e *testExporter) Shutdown(ctx context.Context) error { return nil } func (e *testExporter) ExportSpans(ctx context.Context, ss []trace.ReadOnlySpan) error { e.spans = append(e.spans, ss...) return nil } func NewTestExporter() *testExporter { return &testExporter{} } func TestSpanProcessorAppendsAllBaggageAttributes(t *testing.T) { b, _ := baggage.New() b = addEntryToBaggage(t, b, "baggage.test", "baggage value") ctx := baggage.ContextWithBaggage(context.Background(), b) // create trace provider with baggage processor and test exporter exporter := NewTestExporter() tp := trace.NewTracerProvider( trace.WithSpanProcessor(NewSpanProcessor(AllowAllMembers)), trace.WithSpanProcessor(trace.NewSimpleSpanProcessor(exporter)), ) // create tracer and start/end span tracer := tp.Tracer("test") _, span := tracer.Start(ctx, "test") span.End() require.Len(t, exporter.spans, 1) require.Len(t, exporter.spans[0].Attributes(), 1) want := []attribute.KeyValue{attribute.String("baggage.test", "baggage value")} require.Equal(t, want, exporter.spans[0].Attributes()) } func TestSpanProcessorAppendsBaggageAttributesWithHaPrefixPredicate(t *testing.T) { b, _ := baggage.New() b = addEntryToBaggage(t, b, "baggage.test", "baggage value") ctx := baggage.ContextWithBaggage(context.Background(), b) baggageKeyPredicate := func(m baggage.Member) bool { return strings.HasPrefix(m.Key(), "baggage.") } // create trace provider with baggage processor and test exporter exporter := NewTestExporter() tp := trace.NewTracerProvider( trace.WithSpanProcessor(NewSpanProcessor(baggageKeyPredicate)), trace.WithSpanProcessor(trace.NewSimpleSpanProcessor(exporter)), ) // create tracer and start/end span tracer := tp.Tracer("test") _, span := tracer.Start(ctx, "test") span.End() require.Len(t, exporter.spans, 1) require.Len(t, exporter.spans[0].Attributes(), 1) want := []attribute.KeyValue{attribute.String("baggage.test", "baggage value")} require.Equal(t, want, exporter.spans[0].Attributes()) } func TestSpanProcessorAppendsBaggageAttributesWithRegexPredicate(t *testing.T) { b, _ := baggage.New() b = addEntryToBaggage(t, b, "baggage.test", "baggage value") ctx := baggage.ContextWithBaggage(context.Background(), b) expr := regexp.MustCompile(`^baggage\..*`) baggageKeyPredicate := func(m baggage.Member) bool { return expr.MatchString(m.Key()) } // create trace provider with baggage processor and test exporter exporter := NewTestExporter() tp := trace.NewTracerProvider( trace.WithSpanProcessor(NewSpanProcessor(baggageKeyPredicate)), trace.WithSpanProcessor(trace.NewSimpleSpanProcessor(exporter)), ) // create tracer and start/end span tracer := tp.Tracer("test") _, span := tracer.Start(ctx, "test") span.End() require.Len(t, exporter.spans, 1) require.Len(t, exporter.spans[0].Attributes(), 1) want := []attribute.KeyValue{attribute.String("baggage.test", "baggage value")} require.Equal(t, want, exporter.spans[0].Attributes()) } func TestOnlyAddsBaggageEntriesThatMatchPredicate(t *testing.T) { b, _ := baggage.New() b = addEntryToBaggage(t, b, "baggage.test", "baggage value") b = addEntryToBaggage(t, b, "foo", "bar") ctx := baggage.ContextWithBaggage(context.Background(), b) baggageKeyPredicate := func(m baggage.Member) bool { return m.Key() == "baggage.test" } // create trace provider with baggage processor and test exporter exporter := NewTestExporter() tp := trace.NewTracerProvider( trace.WithSpanProcessor(NewSpanProcessor(baggageKeyPredicate)), trace.WithSpanProcessor(trace.NewSimpleSpanProcessor(exporter)), ) // create tracer and start/end span tracer := tp.Tracer("test") _, span := tracer.Start(ctx, "test") span.End() require.Len(t, exporter.spans, 1) require.Len(t, exporter.spans[0].Attributes(), 1) want := attribute.String("baggage.test", "baggage value") require.Equal(t, want, exporter.spans[0].Attributes()[0]) } func addEntryToBaggage(t *testing.T, b baggage.Baggage, key, value string) baggage.Baggage { member, err := baggage.NewMemberRaw(key, value) require.NoError(t, err) b, err = b.SetMember(member) require.NoError(t, err) return b } func TestZeroSpanProcessorNoPanic(t *testing.T) { sp := new(SpanProcessor) m, err := baggage.NewMember("key", "val") require.NoError(t, err) b, err := baggage.New(m) require.NoError(t, err) ctx := baggage.ContextWithBaggage(context.Background(), b) roS := (tracetest.SpanStub{}).Snapshot() rwS := rwSpan{} assert.NotPanics(t, func() { sp.OnStart(ctx, rwS) sp.OnEnd(roS) _ = sp.ForceFlush(ctx) _ = sp.Shutdown(ctx) }) } type rwSpan struct { trace.ReadWriteSpan } func (s rwSpan) SetAttributes(kv ...attribute.KeyValue) {} open-telemetry-opentelemetry-go-contrib-e5abccb/processors/minsev/000077500000000000000000000000001470323427300257545ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/processors/minsev/example_test.go000066400000000000000000000022201470323427300307710ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package minsev // import "go.opentelemetry.io/contrib/processors/minsev" import ( "context" "fmt" "os" "strings" "sync" "go.opentelemetry.io/otel/log" ) const key = "OTEL_LOG_LEVEL" var getSeverity = sync.OnceValue(func() log.Severity { conv := map[string]log.Severity{ "": log.SeverityInfo, // Default to SeverityInfo for unset. "debug": log.SeverityDebug, "info": log.SeverityInfo, "warn": log.SeverityWarn, "error": log.SeverityError, } // log.SeverityUndefined for unknown values. return conv[strings.ToLower(os.Getenv(key))] }) type EnvSeverity struct{} func (EnvSeverity) Severity() log.Severity { return getSeverity() } func ExampleSeveritier() { // Mock an environment variable setup that would be done externally. _ = os.Setenv(key, "error") p := NewLogProcessor(&processor{}, EnvSeverity{}) ctx, params := context.Background(), log.EnabledParameters{} params.SetSeverity(log.SeverityDebug) fmt.Println(p.Enabled(ctx, params)) params.SetSeverity(log.SeverityError) fmt.Println(p.Enabled(ctx, params)) // Output: // false // true } open-telemetry-opentelemetry-go-contrib-e5abccb/processors/minsev/go.mod000066400000000000000000000013021470323427300270560ustar00rootroot00000000000000module go.opentelemetry.io/contrib/processors/minsev go 1.22 require ( github.com/stretchr/testify v1.9.0 go.opentelemetry.io/otel/log v0.7.0 go.opentelemetry.io/otel/sdk/log v0.7.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/otel v1.31.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect go.opentelemetry.io/otel/sdk v1.31.0 // indirect go.opentelemetry.io/otel/trace v1.31.0 // indirect golang.org/x/sys v0.26.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) open-telemetry-opentelemetry-go-contrib-e5abccb/processors/minsev/go.sum000066400000000000000000000054401470323427300271120ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/log v0.7.0 h1:d1abJc0b1QQZADKvfe9JqqrfmPYQCz2tUSO+0XZmuV4= go.opentelemetry.io/otel/log v0.7.0/go.mod h1:2jf2z7uVfnzDNknKTO9G+ahcOAyWcp1fJmk/wJjULRo= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/sdk/log v0.7.0 h1:dXkeI2S0MLc5g0/AwxTZv6EUEjctiH8aG14Am56NTmQ= go.opentelemetry.io/otel/sdk/log v0.7.0/go.mod h1:oIRXpW+WD6M8BuGj5rtS0aRu/86cbDV/dAfNaZBIjYM= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= open-telemetry-opentelemetry-go-contrib-e5abccb/processors/minsev/minsev.go000066400000000000000000000064641470323427300276160ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package minsev provides an [log.Processor] that will not log any record with // a severity below a configured threshold. package minsev // import "go.opentelemetry.io/contrib/processors/minsev" import ( "context" api "go.opentelemetry.io/otel/log" "go.opentelemetry.io/otel/sdk/log" ) // NewLogProcessor returns a new [LogProcessor] that wraps the downstream // [log.Processor]. // // severity reports the minimum record severity that will be logged. The // LogProcessor discards records with lower severities. If severity is nil, // SeverityInfo is used as a default. The LogProcessor calls severity.Severity // for each record processed or queried; to adjust the minimum level // dynamically, use a [SeverityVar]. // // If downstream is nil a default No-Op [log.Processor] is used. The returned // processor will not be enabled for nor emit any records. func NewLogProcessor(downstream log.Processor, severity Severitier) *LogProcessor { if downstream == nil { downstream = defaultProcessor } if severity == nil { severity = SeverityInfo } p := &LogProcessor{Processor: downstream, sev: severity} if fp, ok := downstream.(filterProcessor); ok { p.filter = fp } return p } // filterProcessor is the experimental optional interface a Processor can // implement (go.opentelemetry.io/otel/sdk/log/internal/x). type filterProcessor interface { Enabled(ctx context.Context, param api.EnabledParameters) bool } // LogProcessor is an [log.Processor] implementation that wraps another // [log.Processor]. It will pass-through calls to OnEmit and Enabled for // records with severity greater than or equal to a minimum. All other method // calls are passed to the wrapped [log.Processor]. // // If the wrapped [log.Processor] is nil, calls to the LogProcessor methods // will panic. Use [NewLogProcessor] to create a new LogProcessor that ensures // no panics. type LogProcessor struct { log.Processor filter filterProcessor sev Severitier } // Compile time assertion that LogProcessor implements log.Processor. var _ log.Processor = (*LogProcessor)(nil) // OnEmit passes ctx and r to the [log.Processor] that p wraps if the severity // of record is greater than or equal to p.Minimum. Otherwise, record is // dropped. func (p *LogProcessor) OnEmit(ctx context.Context, record *log.Record) error { if record.Severity() >= p.sev.Severity() { return p.Processor.OnEmit(ctx, record) } return nil } // Enabled returns if the [log.Processor] that p wraps is enabled if the // severity of param is greater than or equal to p.Minimum. Otherwise false is // returned. func (p *LogProcessor) Enabled(ctx context.Context, param api.EnabledParameters) bool { sev, ok := param.Severity() if !ok { return true } if p.filter != nil { return sev >= p.sev.Severity() && p.filter.Enabled(ctx, param) } return sev >= p.sev.Severity() } var defaultProcessor = noopProcessor{} type noopProcessor struct{} func (p noopProcessor) OnEmit(context.Context, *log.Record) error { return nil } func (p noopProcessor) Enabled(context.Context, api.EnabledParameters) bool { return false } func (p noopProcessor) Shutdown(context.Context) error { return nil } func (p noopProcessor) ForceFlush(context.Context) error { return nil } open-telemetry-opentelemetry-go-contrib-e5abccb/processors/minsev/minsev_test.go000066400000000000000000000162101470323427300306430ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package minsev import ( "context" "testing" "github.com/stretchr/testify/assert" api "go.opentelemetry.io/otel/log" "go.opentelemetry.io/otel/sdk/log" ) var severities = []api.Severity{ api.SeverityTrace, api.SeverityTrace1, api.SeverityTrace2, api.SeverityTrace3, api.SeverityTrace4, api.SeverityDebug, api.SeverityDebug1, api.SeverityDebug2, api.SeverityDebug3, api.SeverityDebug4, api.SeverityInfo, api.SeverityInfo1, api.SeverityInfo2, api.SeverityInfo3, api.SeverityInfo4, api.SeverityWarn, api.SeverityWarn1, api.SeverityWarn2, api.SeverityWarn3, api.SeverityWarn4, api.SeverityError, api.SeverityError1, api.SeverityError2, api.SeverityError3, api.SeverityError4, api.SeverityFatal, api.SeverityFatal1, api.SeverityFatal2, api.SeverityFatal3, api.SeverityFatal4, } type apiSev api.Severity func (s apiSev) Severity() api.Severity { return api.Severity(s) } type emitArgs struct { Ctx context.Context Record *log.Record } type enabledArgs struct { Ctx context.Context Param api.EnabledParameters } type processor struct { ReturnErr error OnEmitCalls []emitArgs EnabledCalls []enabledArgs ForceFlushCalls []context.Context ShutdownCalls []context.Context } func (p *processor) OnEmit(ctx context.Context, r *log.Record) error { p.OnEmitCalls = append(p.OnEmitCalls, emitArgs{ctx, r}) return p.ReturnErr } func (p *processor) Enabled(ctx context.Context, param api.EnabledParameters) bool { p.EnabledCalls = append(p.EnabledCalls, enabledArgs{ctx, param}) return true } func (p *processor) Shutdown(ctx context.Context) error { p.ShutdownCalls = append(p.ShutdownCalls, ctx) return p.ReturnErr } func (p *processor) ForceFlush(ctx context.Context) error { p.ForceFlushCalls = append(p.ForceFlushCalls, ctx) return p.ReturnErr } func (p *processor) Reset() { p.OnEmitCalls = p.OnEmitCalls[:0] p.EnabledCalls = p.EnabledCalls[:0] p.ShutdownCalls = p.ShutdownCalls[:0] p.ForceFlushCalls = p.ForceFlushCalls[:0] } func TestLogProcessorDynamicSeverity(t *testing.T) { sev := new(SeverityVar) wrapped := new(processor) p := NewLogProcessor(wrapped, sev) ctx := context.Background() params := &api.EnabledParameters{} params.SetSeverity(api.SeverityDebug) assert.False(t, p.Enabled(ctx, *params), api.SeverityDebug.String()) params.SetSeverity(api.SeverityInfo) assert.True(t, p.Enabled(ctx, *params), api.SeverityInfo.String()) sev.Set(SeverityError) params.SetSeverity(api.SeverityInfo) assert.False(t, p.Enabled(ctx, *params), api.SeverityInfo.String()) params.SetSeverity(api.SeverityError) assert.True(t, p.Enabled(ctx, *params), api.SeverityError.String()) } func TestLogProcessorOnEmit(t *testing.T) { t.Run("Passthrough", func(t *testing.T) { wrapped := &processor{ReturnErr: assert.AnError} p := NewLogProcessor(wrapped, SeverityTrace1) ctx := context.Background() r := &log.Record{} for _, sev := range severities { r.SetSeverity(sev) assert.ErrorIs(t, p.OnEmit(ctx, r), assert.AnError, sev.String()) if assert.Lenf(t, wrapped.OnEmitCalls, 1, "Record with severity %s not passed-through", sev) { assert.Equal(t, ctx, wrapped.OnEmitCalls[0].Ctx, sev.String()) assert.Equal(t, r, wrapped.OnEmitCalls[0].Record, sev.String()) } wrapped.Reset() } }) t.Run("Dropped", func(t *testing.T) { wrapped := &processor{ReturnErr: assert.AnError} p := NewLogProcessor(wrapped, apiSev(api.SeverityFatal4+1)) ctx := context.Background() r := &log.Record{} for _, sev := range severities { r.SetSeverity(sev) assert.NoError(t, p.OnEmit(ctx, r), sev.String()) if !assert.Emptyf(t, wrapped.OnEmitCalls, "Record with severity %s passed-through", sev) { wrapped.Reset() } } }) } func TestLogProcessorEnabled(t *testing.T) { t.Run("Passthrough", func(t *testing.T) { wrapped := &processor{} p := NewLogProcessor(wrapped, SeverityTrace1) ctx := context.Background() param := api.EnabledParameters{} for _, sev := range severities { param.SetSeverity(sev) assert.True(t, p.Enabled(ctx, param), sev.String()) if assert.Lenf(t, wrapped.EnabledCalls, 1, "Record with severity %s not passed-through", sev) { assert.Equal(t, ctx, wrapped.EnabledCalls[0].Ctx, sev.String()) assert.Equal(t, param, wrapped.EnabledCalls[0].Param, sev.String()) } wrapped.Reset() } }) t.Run("NotEnabled", func(t *testing.T) { wrapped := &processor{} p := NewLogProcessor(wrapped, apiSev(api.SeverityFatal4+1)) ctx := context.Background() param := api.EnabledParameters{} for _, sev := range severities { param.SetSeverity(sev) assert.False(t, p.Enabled(ctx, param), sev.String()) if !assert.Emptyf(t, wrapped.EnabledCalls, "Record with severity %s passed-through", sev) { wrapped.Reset() } } }) t.Run("NoFiltered", func(t *testing.T) { wrapped := &processor{} pruned := struct{ log.Processor }{wrapped} // Remove the Enabled method. p := NewLogProcessor(pruned, SeverityInfo) ctx := context.Background() params := &api.EnabledParameters{} params.SetSeverity(api.SeverityDebug) assert.False(t, p.Enabled(ctx, *params)) params.SetSeverity(api.SeverityInfo) assert.True(t, p.Enabled(ctx, *params)) params.SetSeverity(api.SeverityError) assert.True(t, p.Enabled(ctx, *params)) assert.Empty(t, wrapped.EnabledCalls) }) } func TestLogProcessorForceFlushPassthrough(t *testing.T) { wrapped := &processor{ReturnErr: assert.AnError} p := NewLogProcessor(wrapped, SeverityTrace1) ctx := context.Background() assert.ErrorIs(t, p.ForceFlush(ctx), assert.AnError) assert.Len(t, wrapped.ForceFlushCalls, 1, "ForceFlush not passed-through") } func TestLogProcessorShutdownPassthrough(t *testing.T) { wrapped := &processor{ReturnErr: assert.AnError} p := NewLogProcessor(wrapped, SeverityTrace1) ctx := context.Background() assert.ErrorIs(t, p.Shutdown(ctx), assert.AnError) assert.Len(t, wrapped.ShutdownCalls, 1, "Shutdown not passed-through") } func TestLogProcessorNilSeverity(t *testing.T) { p := NewLogProcessor(nil, nil) assert.Equal(t, SeverityInfo, p.sev.(Severity)) } func TestLogProcessorNilDownstream(t *testing.T) { p := NewLogProcessor(nil, SeverityTrace1) ctx := context.Background() r := new(log.Record) r.SetSeverity(api.SeverityTrace1) param := api.EnabledParameters{} param.SetSeverity(api.SeverityTrace1) assert.NotPanics(t, func() { assert.NoError(t, p.OnEmit(ctx, r)) assert.False(t, p.Enabled(ctx, param)) assert.NoError(t, p.ForceFlush(ctx)) assert.NoError(t, p.Shutdown(ctx)) }) } func BenchmarkLogProcessor(b *testing.B) { r := new(log.Record) r.SetSeverity(api.SeverityTrace) param := api.EnabledParameters{} param.SetSeverity(api.SeverityTrace) ctx := context.Background() type combo interface { log.Processor filterProcessor } run := func(p combo) func(b *testing.B) { return func(b *testing.B) { var err error var enabled bool b.ReportAllocs() for n := 0; n < b.N; n++ { enabled = p.Enabled(ctx, param) err = p.OnEmit(ctx, r) } _, _ = err, enabled } } b.Run("Base", run(defaultProcessor)) b.Run("Enabled", run(NewLogProcessor(nil, SeverityTrace))) b.Run("Disabled", run(NewLogProcessor(nil, SeverityDebug))) } open-telemetry-opentelemetry-go-contrib-e5abccb/processors/minsev/severity.go000066400000000000000000000075331470323427300301650ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package minsev // import "go.opentelemetry.io/contrib/processors/minsev" import ( "sync/atomic" "go.opentelemetry.io/otel/log" ) // Severity represents a log record severity (also known as log level). Smaller // numerical values correspond to less severe log records (such as debug // events), larger numerical values correspond to more severe log records (such // as errors and critical events). type Severity int // Severity values defined by OpenTelemetry. const ( // A fine-grained debugging log record. Typically disabled in default // configurations. SeverityTrace1 Severity = -8 // TRACE SeverityTrace2 Severity = -7 // TRACE2 SeverityTrace3 Severity = -6 // TRACE3 SeverityTrace4 Severity = -5 // TRACE4 // A debugging log record. SeverityDebug1 Severity = -4 // DEBUG SeverityDebug2 Severity = -3 // DEBUG2 SeverityDebug3 Severity = -2 // DEBUG3 SeverityDebug4 Severity = -1 // DEBUG4 // An informational log record. Indicates that an event happened. SeverityInfo1 Severity = 0 // INFO SeverityInfo2 Severity = 1 // INFO2 SeverityInfo3 Severity = 2 // INFO3 SeverityInfo4 Severity = 3 // INFO4 // A warning log record. Not an error but is likely more important than an // informational event. SeverityWarn1 Severity = 4 // WARN SeverityWarn2 Severity = 5 // WARN2 SeverityWarn3 Severity = 6 // WARN3 SeverityWarn4 Severity = 7 // WARN4 // An error log record. Something went wrong. SeverityError1 Severity = 8 // ERROR SeverityError2 Severity = 9 // ERROR2 SeverityError3 Severity = 10 // ERROR3 SeverityError4 Severity = 11 // ERROR4 // A fatal log record such as application or system crash. SeverityFatal1 Severity = 12 // FATAL SeverityFatal2 Severity = 13 // FATAL2 SeverityFatal3 Severity = 14 // FATAL3 SeverityFatal4 Severity = 15 // FATAL4 // Convenience definitions for the base severity of each level. SeverityTrace = SeverityTrace1 SeverityDebug = SeverityDebug1 SeverityInfo = SeverityInfo1 SeverityWarn = SeverityWarn1 SeverityError = SeverityError1 SeverityFatal = SeverityFatal1 ) // Severity returns the receiver translated to a [log.Severity]. // // It implements [Severitier]. func (s Severity) Severity() log.Severity { // Unknown defaults to log.SeverityUndefined. return translations[s] } var translations = map[Severity]log.Severity{ SeverityTrace1: log.SeverityTrace1, SeverityTrace2: log.SeverityTrace2, SeverityTrace3: log.SeverityTrace3, SeverityTrace4: log.SeverityTrace4, SeverityDebug1: log.SeverityDebug1, SeverityDebug2: log.SeverityDebug2, SeverityDebug3: log.SeverityDebug3, SeverityDebug4: log.SeverityDebug4, SeverityInfo1: log.SeverityInfo1, SeverityInfo2: log.SeverityInfo2, SeverityInfo3: log.SeverityInfo3, SeverityInfo4: log.SeverityInfo4, SeverityWarn1: log.SeverityWarn1, SeverityWarn2: log.SeverityWarn2, SeverityWarn3: log.SeverityWarn3, SeverityWarn4: log.SeverityWarn4, SeverityError1: log.SeverityError1, SeverityError2: log.SeverityError2, SeverityError3: log.SeverityError3, SeverityError4: log.SeverityError4, SeverityFatal1: log.SeverityFatal1, SeverityFatal2: log.SeverityFatal2, SeverityFatal3: log.SeverityFatal3, SeverityFatal4: log.SeverityFatal4, } // A SeverityVar is a [Severity] variable, to allow a [LogProcessor] severity // to change dynamically. It implements [Severitier] as well as a Set method, // and it is safe for use by multiple goroutines. // // The zero SeverityVar corresponds to [SeverityInfo]. type SeverityVar struct { val atomic.Int64 } // Severity returns v's severity. func (v *SeverityVar) Severity() log.Severity { return Severity(int(v.val.Load())).Severity() } // Set sets v's Severity to l. func (v *SeverityVar) Set(l Severity) { v.val.Store(int64(l)) } // A Severitier provides a [log.Severity] value. type Severitier interface { Severity() log.Severity } open-telemetry-opentelemetry-go-contrib-e5abccb/processors/minsev/severity_test.go000066400000000000000000000011521470323427300312130ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package minsev // import "go.opentelemetry.io/contrib/processors/minsev" import ( "sync" "testing" "go.opentelemetry.io/otel/log" ) func TestSeverityVarConcurrentSafe(t *testing.T) { var ( sev SeverityVar wg sync.WaitGroup ) wg.Add(1) go func() { defer wg.Done() for s := SeverityTrace1; s <= SeverityFatal4; s++ { sev.Set(s) } }() wg.Add(1) go func() { defer wg.Done() var got log.Severity for i := SeverityFatal4 - SeverityTrace1; i >= 0; i-- { got = sev.Severity() } _ = got }() wg.Wait() } open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/000077500000000000000000000000001470323427300246125ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/autoprop/000077500000000000000000000000001470323427300264635ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/autoprop/doc.go000066400000000000000000000013111470323427300275530ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package autoprop provides an OpenTelemetry TextMapPropagator creation // function. The OpenTelemetry specification states that the default // TextMapPropagator needs to be a no-operation implementation. The // opentelemetry-go project adheres to this requirement. However, for systems // that perform propagation this default is not ideal. This package provides a // TextMapPropagator with useful defaults (a combined TraceContext and Baggage // TextMapPropagator), and supports environment overrides using the // OTEL_PROPAGATORS environment variable. package autoprop // import "go.opentelemetry.io/contrib/propagators/autoprop" open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/autoprop/example_test.go000066400000000000000000000060001470323427300315000ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package autoprop_test import ( "fmt" "os" "sort" "go.opentelemetry.io/contrib/propagators/autoprop" "go.opentelemetry.io/contrib/propagators/b3" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/propagation" ) func ExampleNewTextMapPropagator() { // NewTextMapPropagator returns a TraceContext and Baggage propagator by // default. The response of this function can be directly registered with // the go.opentelemetry.io/otel package. otel.SetTextMapPropagator(autoprop.NewTextMapPropagator()) fields := otel.GetTextMapPropagator().Fields() sort.Strings(fields) fmt.Println(fields) // Output: [baggage traceparent tracestate] } func ExampleNewTextMapPropagator_arguments() { // NewTextMapPropagator behaves the same as the // NewCompositeTextMapPropagator function in the // go.opentelemetry.io/otel/propagation package when TextMapPropagator are // passed as arguments. fields := autoprop.NewTextMapPropagator( propagation.TraceContext{}, propagation.Baggage{}, b3.New(), ).Fields() sort.Strings(fields) fmt.Println(fields) // Output: [baggage traceparent tracestate x-b3-flags x-b3-sampled x-b3-spanid x-b3-traceid] } func ExampleNewTextMapPropagator_environment() { // Propagators set for the OTEL_PROPAGATORS environment variable take // precedence and will override any arguments passed to // NewTextMapPropagator. _ = os.Setenv("OTEL_PROPAGATORS", "b3,baggage") // Returns only a B3 and Baggage TextMapPropagator (i.e. does not include // TraceContext). fields := autoprop.NewTextMapPropagator(propagation.TraceContext{}).Fields() sort.Strings(fields) fmt.Println(fields) // Output: [baggage x-b3-flags x-b3-sampled x-b3-spanid x-b3-traceid] } type myTextMapPropagator struct{ propagation.TextMapPropagator } func (myTextMapPropagator) Fields() []string { return []string{"my-header-val"} } func ExampleRegisterTextMapPropagator() { // To use your own or a 3rd-party exporter via the OTEL_PROPAGATORS // environment variable, it needs to be registered prior to calling // NewTextMapPropagator. autoprop.RegisterTextMapPropagator("custom-prop", myTextMapPropagator{}) _ = os.Setenv("OTEL_PROPAGATORS", "custom-prop") fmt.Println(autoprop.NewTextMapPropagator().Fields()) // Output: [my-header-val] } func ExampleGetTextMapPropagator() { prop, err := autoprop.TextMapPropagator("b3", "baggage") if err != nil { // Handle error appropriately. panic(err) } fields := prop.Fields() sort.Strings(fields) fmt.Println(fields) // Output: [baggage x-b3-flags x-b3-sampled x-b3-spanid x-b3-traceid] } func ExampleGetTextMapPropagator_custom() { // To use your own or a 3rd-party exporter it needs to be registered prior // to calling GetTextMapPropagator. autoprop.RegisterTextMapPropagator("custom-get-prop", myTextMapPropagator{}) prop, err := autoprop.TextMapPropagator("custom-get-prop") if err != nil { // Handle error appropriately. panic(err) } fmt.Println(prop.Fields()) // Output: [my-header-val] } open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/autoprop/go.mod000066400000000000000000000021501470323427300275670ustar00rootroot00000000000000module go.opentelemetry.io/contrib/propagators/autoprop go 1.22 require ( github.com/stretchr/testify v1.9.0 go.opentelemetry.io/contrib/propagators/aws v1.31.0 go.opentelemetry.io/contrib/propagators/b3 v1.31.0 go.opentelemetry.io/contrib/propagators/jaeger v1.31.0 go.opentelemetry.io/contrib/propagators/ot v1.31.0 go.opentelemetry.io/otel v1.31.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect go.opentelemetry.io/otel/sdk v1.31.0 // indirect go.opentelemetry.io/otel/trace v1.31.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/sys v0.26.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) replace go.opentelemetry.io/contrib/propagators/jaeger => ../jaeger replace go.opentelemetry.io/contrib/propagators/b3 => ../b3 replace go.opentelemetry.io/contrib/propagators/aws => ../aws replace go.opentelemetry.io/contrib/propagators/ot => ../ot open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/autoprop/go.sum000066400000000000000000000051331470323427300276200ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/autoprop/propagator.go000066400000000000000000000055651470323427300312030ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package autoprop // import "go.opentelemetry.io/contrib/propagators/autoprop" import ( "errors" "os" "strings" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/propagation" ) // otelPropagatorsEnvKey is the environment variable name identifying // propagators to use. const otelPropagatorsEnvKey = "OTEL_PROPAGATORS" // NewTextMapPropagator returns a new TextMapPropagator composited by props or // one defined by the OTEL_PROPAGATORS environment variable. The // TextMapPropagator defined by OTEL_PROPAGATORS, if set, will take precedence // to the once composited by props. // // The propagators supported with the OTEL_PROPAGATORS environment variable by // default are: tracecontext, baggage, b3, b3multi, jaeger, xray, ottrace, and // none. Each of these values, and their combination, are supported in // conformance with the OpenTelemetry specification. See // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/sdk-environment-variables.md#general-sdk-configuration // for more information. // // The supported environment variable propagators can be extended to include // custom 3rd-party TextMapPropagator. See the RegisterTextMapPropagator // function for more information. // // If OTEL_PROPAGATORS is not defined and props is no provided, the returned // TextMapPropagator will be a composite of the TraceContext and Baggage // propagators. func NewTextMapPropagator(props ...propagation.TextMapPropagator) propagation.TextMapPropagator { // Environment variable defined propagator has precedence over arguments. envProp, err := parseEnv() if err != nil { // Communicate to the user their supplied value will not be used. otel.Handle(err) } if envProp != nil { return envProp } switch len(props) { case 0: // Default to TraceContext and Baggage. return propagation.NewCompositeTextMapPropagator( propagation.TraceContext{}, propagation.Baggage{}, ) case 1: // Do not add overhead with a composite propagator wrapping a single // propagator, return it directly. return props[0] default: return propagation.NewCompositeTextMapPropagator(props...) } } // errUnknownPropagator is returned when an unknown propagator name is used in // the OTEL_PROPAGATORS environment variable. var errUnknownPropagator = errors.New("unknown propagator") // parseEnv returns the composite TextMapPropagators defined by the // OTEL_PROPAGATORS environment variable. A nil TextMapPropagator is returned // if no propagator is defined for the environment variable. A no-op // TextMapPropagator will be returned if "none" is defined anywhere in the // environment variable. func parseEnv() (propagation.TextMapPropagator, error) { propStrs := os.Getenv(otelPropagatorsEnvKey) if propStrs == "" { return nil, nil } return TextMapPropagator(strings.Split(propStrs, ",")...) } open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/autoprop/propagator_test.go000066400000000000000000000024421470323427300322310ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package autoprop import ( "context" "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/propagation" ) type handler struct { err error } func (h *handler) Handle(err error) { h.err = err } func TestNewTextMapPropagatorInvalidEnvVal(t *testing.T) { h := &handler{} otel.SetErrorHandler(h) const name = "invalid-name" t.Setenv(otelPropagatorsEnvKey, name) _ = NewTextMapPropagator() assert.ErrorIs(t, h.err, errUnknownPropagator) } func TestNewTextMapPropagatorDefault(t *testing.T) { expect := []string{"traceparent", "tracestate", "baggage"} assert.ElementsMatch(t, expect, NewTextMapPropagator().Fields()) } type ptrNoop struct{} func (*ptrNoop) Inject(context.Context, propagation.TextMapCarrier) {} func (*ptrNoop) Extract(context.Context, propagation.TextMapCarrier) context.Context { return context.Background() } func (*ptrNoop) Fields() []string { return nil } func TestNewTextMapPropagatorSingleNoOverhead(t *testing.T) { p := &ptrNoop{} assert.Same(t, p, NewTextMapPropagator(p)) } func TestNewTextMapPropagatorMultiEnvNone(t *testing.T) { t.Setenv(otelPropagatorsEnvKey, "b3,none,tracecontext") assert.Equal(t, noop, NewTextMapPropagator()) } open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/autoprop/registry.go000066400000000000000000000116401470323427300306640ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package autoprop // import "go.opentelemetry.io/contrib/propagators/autoprop" import ( "errors" "fmt" "strings" "sync" "go.opentelemetry.io/contrib/propagators/aws/xray" "go.opentelemetry.io/contrib/propagators/b3" "go.opentelemetry.io/contrib/propagators/jaeger" "go.opentelemetry.io/contrib/propagators/ot" "go.opentelemetry.io/otel/propagation" ) // none is the special "propagator" name that means no propagator shall be // configured. const none = "none" // propagators is the registry of TextMapPropagators registered with this // package. It includes all the OpenTelemetry defaults at startup. var propagators = ®istry{ names: map[string]propagation.TextMapPropagator{ // W3C Trace Context. "tracecontext": propagation.TraceContext{}, // W3C Baggage. "baggage": propagation.Baggage{}, // B3 single-header format. "b3": b3.New(), // B3 multi-header format. "b3multi": b3.New(b3.WithInjectEncoding(b3.B3MultipleHeader)), // Jaeger. "jaeger": jaeger.Jaeger{}, // AWS X-Ray. "xray": xray.Propagator{}, // OpenTracing Trace. "ottrace": ot.OT{}, // No-op TextMapPropagator. none: propagation.NewCompositeTextMapPropagator(), }, } // registry maintains a map of propagator names to TextMapPropagator // implementations that is safe for concurrent use by multiple goroutines // without additional locking or coordination. type registry struct { mu sync.Mutex names map[string]propagation.TextMapPropagator } // load returns the value stored in the registry index for a key, or nil if no // value is present. The ok result indicates whether value was found in the // index. func (r *registry) load(key string) (p propagation.TextMapPropagator, ok bool) { r.mu.Lock() p, ok = r.names[key] r.mu.Unlock() return p, ok } var errDupReg = errors.New("duplicate registration") // store sets the value for a key if is not already in the registry. errDupReg // is returned if the registry already contains key. func (r *registry) store(key string, value propagation.TextMapPropagator) error { r.mu.Lock() defer r.mu.Unlock() if r.names == nil { r.names = map[string]propagation.TextMapPropagator{key: value} return nil } if _, ok := r.names[key]; ok { return fmt.Errorf("%w: %q", errDupReg, key) } r.names[key] = value return nil } // drop removes key from the registry if it exists, otherwise nothing. func (r *registry) drop(key string) { r.mu.Lock() delete(r.names, key) r.mu.Unlock() } // RegisterTextMapPropagator sets the TextMapPropagator p to be used when the // OTEL_PROPAGATORS environment variable contains the propagator name. This // will panic if name has already been registered or is a default // (tracecontext, baggage, b3, b3multi, jaeger, xray, or ottrace). func RegisterTextMapPropagator(name string, p propagation.TextMapPropagator) { if err := propagators.store(name, p); err != nil { // envRegistry.store will return errDupReg if name is already // registered. Panic here so the user is made aware of the duplicate // registration, which could be done by malicious code trying to // intercept cross-cutting concerns. // // Panic for all other errors as well. At this point there should not // be any other errors returned from the store operation. If there // are, alert the developer that adding them as soon as possible that // they need to be handled here. panic(err) } } // TextMapPropagator returns a TextMapPropagator composed from the // passed names of registered TextMapPropagators. Each name must match an // already registered TextMapPropagator (see the RegisterTextMapPropagator // function for more information) or a default (tracecontext, baggage, b3, // b3multi, jaeger, xray, or ottrace). // // If "none" is included in the arguments, or no names are provided, the // returned TextMapPropagator will be a no-operation implementation. // // An error is returned for any un-registered names. The remaining, known, // names will be used to compose a TextMapPropagator that is returned with the // error. func TextMapPropagator(names ...string) (propagation.TextMapPropagator, error) { var ( props []propagation.TextMapPropagator unknown []string ) for _, name := range names { if name == none { // If "none" is passed in combination with any other propagator, // the result still needs to be a no-op propagator. Therefore, // short-circuit here. return propagation.NewCompositeTextMapPropagator(), nil } p, ok := propagators.load(name) if !ok { unknown = append(unknown, name) continue } props = append(props, p) } var err error if len(unknown) > 0 { joined := strings.Join(unknown, ",") err = fmt.Errorf("%w: %s", errUnknownPropagator, joined) } switch len(props) { case 0: return nil, err case 1: // Do not return a composite of a single propagator. return props[0], err default: return propagation.NewCompositeTextMapPropagator(props...), err } } open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/autoprop/registry_test.go000066400000000000000000000036171470323427300317300ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package autoprop import ( "fmt" "sync" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/propagation" ) var noop = propagation.NewCompositeTextMapPropagator() func TestRegistryEmptyStore(t *testing.T) { r := registry{} assert.NotPanics(t, func() { require.NoError(t, r.store("first", noop)) }) } func TestRegistryEmptyLoad(t *testing.T) { r := registry{} assert.NotPanics(t, func() { v, ok := r.load("non-existent") assert.False(t, ok, "empty registry should hold nothing") assert.Nil(t, v, "non-nil propagator returned") }) } func TestRegistryConcurrentSafe(t *testing.T) { const propName = "prop" r := registry{} assert.NotPanics(t, func() { require.NoError(t, r.store(propName, noop)) }) var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() assert.NotPanics(t, func() { require.ErrorIs(t, r.store(propName, noop), errDupReg) }) }() wg.Add(1) go func() { defer wg.Done() assert.NotPanics(t, func() { v, ok := r.load(propName) assert.True(t, ok, "missing propagator in registry") assert.Equal(t, noop, v, "wrong propagator returned") }) }() wg.Wait() } func TestRegisterTextMapPropagator(t *testing.T) { const propName = "custom" RegisterTextMapPropagator(propName, noop) t.Cleanup(func() { propagators.drop(propName) }) v, ok := propagators.load(propName) assert.True(t, ok, "missing propagator in envRegistry") assert.Equal(t, noop, v, "wrong propagator stored") } func TestDuplicateRegisterTextMapPropagatorPanics(t *testing.T) { const propName = "custom" RegisterTextMapPropagator(propName, noop) t.Cleanup(func() { propagators.drop(propName) }) errString := fmt.Sprintf("%s: %q", errDupReg, propName) assert.PanicsWithError(t, errString, func() { RegisterTextMapPropagator(propName, noop) }) } open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/aws/000077500000000000000000000000001470323427300254045ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/aws/go.mod000066400000000000000000000011161470323427300265110ustar00rootroot00000000000000module go.opentelemetry.io/contrib/propagators/aws go 1.22 require ( github.com/stretchr/testify v1.9.0 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/sdk v1.31.0 go.opentelemetry.io/otel/trace v1.31.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect golang.org/x/sys v0.26.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/aws/go.sum000066400000000000000000000046721470323427300265500ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/aws/version.go000066400000000000000000000007641470323427300274270ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package aws // import "go.opentelemetry.io/contrib/propagators/aws" // Version is the current release version of the AWS XRay propagator. func Version() string { return "1.31.0" // This string is updated by the pre_release.sh script during release } // SemVersion is the semantic version to be supplied to tracer/meter creation. // // Deprecated: Use [Version] instead. func SemVersion() string { return Version() } open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/aws/xray/000077500000000000000000000000001470323427300263675ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/aws/xray/README.MD000066400000000000000000000006061470323427300275500ustar00rootroot00000000000000# AWS X-Ray Propagator/IDGenerator This package contains an AWS X-Ray compatible `TextMapPropagator` and `IDGenerator`. ## `traceIdRatioSampler` and `x-ray IDGenerator` compatibility It is a general suggestion to **not** use the `traceIDRatioSampler` while also using the X-Ray `IDGenerator`. The non-random nature of building an X-Ray `traceId` may lead to unexpected sampling results. open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/aws/xray/idgenerator.go000066400000000000000000000043101470323427300312170ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package xray // import "go.opentelemetry.io/contrib/propagators/aws/xray" import ( "context" crand "crypto/rand" "encoding/binary" "encoding/hex" "math/rand" "strconv" "sync" "time" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" ) // IDGenerator is used for generating a new traceID and spanID. type IDGenerator struct { sync.Mutex randSource *rand.Rand } var _ sdktrace.IDGenerator = &IDGenerator{} // NewSpanID returns a non-zero span ID from a randomly-chosen sequence. func (gen *IDGenerator) NewSpanID(ctx context.Context, traceID trace.TraceID) trace.SpanID { gen.Lock() defer gen.Unlock() sid := trace.SpanID{} _, _ = gen.randSource.Read(sid[:]) return sid } // NewIDs returns a non-zero trace ID and a non-zero span ID. // trace ID returned is based on AWS X-Ray TraceID format. // - https://docs.aws.amazon.com/xray/latest/devguide/xray-api-sendingdata.html#xray-api-traceids // // span ID is from a randomly-chosen sequence. func (gen *IDGenerator) NewIDs(ctx context.Context) (trace.TraceID, trace.SpanID) { gen.Lock() defer gen.Unlock() tid := trace.TraceID{} currentTime := getCurrentTimeHex() copy(tid[:4], currentTime) _, _ = gen.randSource.Read(tid[4:]) sid := trace.SpanID{} _, _ = gen.randSource.Read(sid[:]) return tid, sid } // NewIDGenerator returns an IDGenerator reference used for sending traces to AWS X-Ray. func NewIDGenerator() *IDGenerator { gen := &IDGenerator{} var rngSeed int64 _ = binary.Read(crand.Reader, binary.LittleEndian, &rngSeed) gen.randSource = rand.New(rand.NewSource(rngSeed)) //nolint:gosec // G404: Use of weak random number generator (math/rand instead of crypto/rand) is ignored as this is not security-sensitive. return gen } func getCurrentTimeHex() []uint8 { currentTime := time.Now().Unix() // Ignore error since no expected error should result from this operation // Odd-length strings and non-hex digits are the only 2 error conditions for hex.DecodeString() // strconv.FromatInt() do not produce odd-length strings or non-hex digits currentTimeHex, _ := hex.DecodeString(strconv.FormatInt(currentTime, 16)) return currentTimeHex } open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/aws/xray/idgenerator_benchmark_test.go000066400000000000000000000015251470323427300342750ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package xray import ( "context" "testing" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" ) var tracer trace.Tracer func init() { idg := NewIDGenerator() tracer = sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithIDGenerator(idg), ).Tracer("sample-app") } func BenchmarkStartAndEndSampledSpan(b *testing.B) { for i := 0; i < b.N; i++ { _, span := tracer.Start(context.Background(), "Example Trace") span.End() } } func BenchmarkStartAndEndNestedSampledSpan(b *testing.B) { ctx, parent := tracer.Start(context.Background(), "Parent operation...") defer parent.End() b.ResetTimer() for i := 0; i < b.N; i++ { _, span := tracer.Start(ctx, "Sub operation...") span.End() } } open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/aws/xray/idgenerator_test.go000066400000000000000000000051751470323427300322700ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package xray import ( "bytes" "context" "strconv" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/trace" ) func TestTraceIDIsValidLength(t *testing.T) { idg := NewIDGenerator() traceID, _ := idg.NewIDs(context.Background()) expectedTraceIDLength := 32 assert.Len(t, traceID.String(), expectedTraceIDLength, "TraceID has incorrect length.") } func TestTraceIDIsUnique(t *testing.T) { idg := NewIDGenerator() traceID1, _ := idg.NewIDs(context.Background()) traceID2, _ := idg.NewIDs(context.Background()) assert.NotEqual(t, traceID1.String(), traceID2.String(), "TraceID should be unique") } func TestTraceIDTimestampInBounds(t *testing.T) { idg := NewIDGenerator() previousTime := time.Now().Unix() traceID, _ := idg.NewIDs(context.Background()) currentTime, err := strconv.ParseInt(traceID.String()[0:8], 16, 64) require.NoError(t, err) nextTime := time.Now().Unix() assert.LessOrEqual(t, previousTime, currentTime, "TraceID is generated incorrectly with the wrong timestamp.") assert.LessOrEqual(t, currentTime, nextTime, "TraceID is generated incorrectly with the wrong timestamp.") } func TestTraceIDIsNotNil(t *testing.T) { var nilTraceID trace.TraceID idg := NewIDGenerator() traceID, _ := idg.NewIDs(context.Background()) assert.False(t, bytes.Equal(traceID[:], nilTraceID[:]), "TraceID cannot be empty.") } func TestSpanIDIsValidLength(t *testing.T) { idg := NewIDGenerator() ctx := context.Background() traceID, spanID1 := idg.NewIDs(ctx) spanID2 := idg.NewSpanID(context.Background(), traceID) expectedSpanIDLength := 16 assert.Len(t, spanID1.String(), expectedSpanIDLength, "SpanID has incorrect length") assert.Len(t, spanID2.String(), expectedSpanIDLength, "SpanID has incorrect length") } func TestSpanIDIsUnique(t *testing.T) { idg := NewIDGenerator() ctx := context.Background() traceID, spanID1 := idg.NewIDs(ctx) _, spanID2 := idg.NewIDs(ctx) spanID3 := idg.NewSpanID(ctx, traceID) spanID4 := idg.NewSpanID(ctx, traceID) assert.NotEqual(t, spanID1.String(), spanID2.String(), "SpanID should be unique") assert.NotEqual(t, spanID3.String(), spanID4.String(), "SpanID should be unique") } func TestSpanIDIsNotNil(t *testing.T) { var nilSpanID trace.SpanID idg := NewIDGenerator() ctx := context.Background() traceID, spanID1 := idg.NewIDs(ctx) spanID2 := idg.NewSpanID(ctx, traceID) assert.False(t, bytes.Equal(spanID1[:], nilSpanID[:]), "SpanID cannot be empty.") assert.False(t, bytes.Equal(spanID2[:], nilSpanID[:]), "SpanID cannot be empty.") } open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/aws/xray/propagator.go000066400000000000000000000126621470323427300311030ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package xray // import "go.opentelemetry.io/contrib/propagators/aws/xray" import ( "context" "errors" "strings" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" ) const ( traceHeaderKey = "X-Amzn-Trace-Id" traceHeaderDelimiter = ";" kvDelimiter = "=" traceIDKey = "Root" sampleFlagKey = "Sampled" parentIDKey = "Parent" traceIDVersion = "1" traceIDDelimiter = "-" isSampled = "1" notSampled = "0" traceFlagNone = 0x0 traceFlagSampled = 0x1 << 0 traceIDLength = 35 traceIDDelimitterIndex1 = 1 traceIDDelimitterIndex2 = 10 traceIDFirstPartLength = 8 sampledFlagLength = 1 ) var ( empty = trace.SpanContext{} errInvalidTraceHeader = errors.New("invalid X-Amzn-Trace-Id header value, should contain 3 different part separated by ;") errMalformedTraceID = errors.New("cannot decode trace ID from header") errLengthTraceIDHeader = errors.New("incorrect length of X-Ray trace ID found, 35 character length expected") errInvalidTraceIDVersion = errors.New("invalid X-Ray trace ID header found, does not have valid trace ID version") errInvalidSpanIDLength = errors.New("invalid span ID length, must be 16") ) // Propagator serializes Span Context to/from AWS X-Ray headers. // // Example AWS X-Ray format: // // X-Amzn-Trace-Id: Root={traceId};Parent={parentId};Sampled={samplingFlag}. type Propagator struct{} // Asserts that the propagator implements the otel.TextMapPropagator interface at compile time. var _ propagation.TextMapPropagator = &Propagator{} // Inject injects a context to the carrier following AWS X-Ray format. func (xray Propagator) Inject(ctx context.Context, carrier propagation.TextMapCarrier) { sc := trace.SpanFromContext(ctx).SpanContext() if !sc.TraceID().IsValid() || !sc.SpanID().IsValid() { return } otTraceID := sc.TraceID().String() xrayTraceID := traceIDVersion + traceIDDelimiter + otTraceID[0:traceIDFirstPartLength] + traceIDDelimiter + otTraceID[traceIDFirstPartLength:] parentID := sc.SpanID() samplingFlag := notSampled if sc.TraceFlags() == traceFlagSampled { samplingFlag = isSampled } headers := []string{ traceIDKey, kvDelimiter, xrayTraceID, traceHeaderDelimiter, parentIDKey, kvDelimiter, parentID.String(), traceHeaderDelimiter, sampleFlagKey, kvDelimiter, samplingFlag, } carrier.Set(traceHeaderKey, strings.Join(headers, "")) } // Extract gets a context from the carrier if it contains AWS X-Ray headers. func (xray Propagator) Extract(ctx context.Context, carrier propagation.TextMapCarrier) context.Context { // extract tracing information if header := carrier.Get(traceHeaderKey); header != "" { sc, err := extract(header) if err == nil && sc.IsValid() { return trace.ContextWithRemoteSpanContext(ctx, sc) } } return ctx } // extract extracts Span Context from context. func extract(headerVal string) (trace.SpanContext, error) { var ( scc = trace.SpanContextConfig{} err error delimiterIndex int part string ) pos := 0 for pos < len(headerVal) { delimiterIndex = indexOf(headerVal, traceHeaderDelimiter, pos) if delimiterIndex >= 0 { part = headerVal[pos:delimiterIndex] pos = delimiterIndex + 1 } else { // last part part = strings.TrimSpace(headerVal[pos:]) pos = len(headerVal) } equalsIndex := strings.Index(part, kvDelimiter) if equalsIndex < 0 { return empty, errInvalidTraceHeader } value := part[equalsIndex+1:] if strings.HasPrefix(part, traceIDKey) { scc.TraceID, err = parseTraceID(value) if err != nil { return empty, err } } else if strings.HasPrefix(part, parentIDKey) { // extract parentId scc.SpanID, err = trace.SpanIDFromHex(value) if err != nil { return empty, errInvalidSpanIDLength } } else if strings.HasPrefix(part, sampleFlagKey) { // extract traceflag scc.TraceFlags = parseTraceFlag(value) } } return trace.NewSpanContext(scc), nil } // indexOf returns position of the first occurrence of a substr in str starting at pos index. func indexOf(str string, substr string, pos int) int { index := strings.Index(str[pos:], substr) if index > -1 { index += pos } return index } // parseTraceID returns trace ID if valid else return invalid trace ID. func parseTraceID(xrayTraceID string) (trace.TraceID, error) { if len(xrayTraceID) != traceIDLength { return empty.TraceID(), errLengthTraceIDHeader } if !strings.HasPrefix(xrayTraceID, traceIDVersion) { return empty.TraceID(), errInvalidTraceIDVersion } if xrayTraceID[traceIDDelimitterIndex1:traceIDDelimitterIndex1+1] != traceIDDelimiter || xrayTraceID[traceIDDelimitterIndex2:traceIDDelimitterIndex2+1] != traceIDDelimiter { return empty.TraceID(), errMalformedTraceID } epochPart := xrayTraceID[traceIDDelimitterIndex1+1 : traceIDDelimitterIndex2] uniquePart := xrayTraceID[traceIDDelimitterIndex2+1 : traceIDLength] result := epochPart + uniquePart return trace.TraceIDFromHex(result) } // parseTraceFlag returns a parsed trace flag. func parseTraceFlag(xraySampledFlag string) trace.TraceFlags { if len(xraySampledFlag) == sampledFlagLength && xraySampledFlag != isSampled { return traceFlagNone } return trace.FlagsSampled } // Fields returns list of fields used by HTTPTextFormat. func (xray Propagator) Fields() []string { return []string{traceHeaderKey} } open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/aws/xray/propagator_test.go000066400000000000000000000060251470323427300321360ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package xray import ( "context" "net/http" "strings" "testing" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/propagation" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/trace" ) var ( traceID = trace.TraceID{0x8a, 0x3c, 0x60, 0xf7, 0xd1, 0x88, 0xf8, 0xfa, 0x79, 0xd4, 0x8a, 0x39, 0x1a, 0x77, 0x8f, 0xa6} xrayTraceID = "1-8a3c60f7-d188f8fa79d48a391a778fa6" xrayTraceIDIncorrectLength = "1-82138-1203123" parentID64Str = "53995c3f42cd8ad8" parentSpanID = trace.SpanID{0x53, 0x99, 0x5c, 0x3f, 0x42, 0xcd, 0x8a, 0xd8} zeroSpanIDStr = "0000000000000000" wrongVersionTraceHeaderID = "5b00000000b000000000000000000000000" ) func TestAwsXrayExtract(t *testing.T) { testData := []struct { traceID string parentSpanID string samplingFlag string expected trace.SpanContextConfig err error }{ { xrayTraceID, parentID64Str, notSampled, trace.SpanContextConfig{ TraceID: traceID, SpanID: parentSpanID, TraceFlags: traceFlagNone, }, nil, }, { xrayTraceID, parentID64Str, isSampled, trace.SpanContextConfig{ TraceID: traceID, SpanID: parentSpanID, TraceFlags: traceFlagSampled, }, nil, }, { xrayTraceID, zeroSpanIDStr, isSampled, trace.SpanContextConfig{}, errInvalidSpanIDLength, }, { xrayTraceIDIncorrectLength, parentID64Str, isSampled, trace.SpanContextConfig{}, errLengthTraceIDHeader, }, { wrongVersionTraceHeaderID, parentID64Str, isSampled, trace.SpanContextConfig{}, errInvalidTraceIDVersion, }, } for _, test := range testData { headerVal := strings.Join([]string{ traceIDKey, kvDelimiter, test.traceID, traceHeaderDelimiter, parentIDKey, kvDelimiter, test.parentSpanID, traceHeaderDelimiter, sampleFlagKey, kvDelimiter, test.samplingFlag, }, "") sc, err := extract(headerVal) info := []interface{}{ "trace ID: %q, parent span ID: %q, sampling flag: %q", test.traceID, test.parentSpanID, test.samplingFlag, } if !assert.Equal(t, test.err, err, info...) { continue } assert.Equal(t, trace.NewSpanContext(test.expected), sc, info...) } } func BenchmarkPropagatorExtract(b *testing.B) { propagator := Propagator{} ctx := context.Background() req, _ := http.NewRequest("GET", "http://example.com", nil) req.Header.Set("Root", "1-8a3c60f7-d188f8fa79d48a391a778fa6") req.Header.Set("Parent", "53995c3f42cd8ad8") req.Header.Set("Sampled", "1") b.ResetTimer() for i := 0; i < b.N; i++ { _ = propagator.Extract(ctx, propagation.HeaderCarrier(req.Header)) } } func BenchmarkPropagatorInject(b *testing.B) { propagator := Propagator{} tracer := otel.Tracer("test") req, _ := http.NewRequest("GET", "http://example.com", nil) ctx, _ := tracer.Start(context.Background(), "Parent operation...") b.ResetTimer() for i := 0; i < b.N; i++ { propagator.Inject(ctx, propagation.HeaderCarrier(req.Header)) } } open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/b3/000077500000000000000000000000001470323427300251165ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/b3/b3_benchmark_test.go000066400000000000000000000036401470323427300310250ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package b3_test import ( "context" "net/http" "testing" "go.opentelemetry.io/contrib/propagators/b3" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" ) func BenchmarkExtractB3(b *testing.B) { testGroup := []struct { name string tests []extractTest }{ { name: "valid headers", tests: extractHeaders, }, { name: "invalid headers", tests: extractInvalidHeaders, }, } for _, tg := range testGroup { propagator := b3.New() for _, tt := range tg.tests { traceBenchmark(tg.name+"/"+tt.name, b, func(b *testing.B) { ctx := context.Background() req, _ := http.NewRequest("GET", "http://example.com", nil) for h, v := range tt.headers { req.Header.Set(h, v) } b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { _ = propagator.Extract(ctx, propagation.HeaderCarrier(req.Header)) } }) } } } func BenchmarkInjectB3(b *testing.B) { testGroup := []struct { name string tests []injectTest }{ { name: "valid headers", tests: injectHeader, }, { name: "invalid headers", tests: injectInvalidHeader, }, } for _, tg := range testGroup { for _, tt := range tg.tests { propagator := b3.New(b3.WithInjectEncoding(tt.encoding)) traceBenchmark(tg.name+"/"+tt.name, b, func(b *testing.B) { req, _ := http.NewRequest("GET", "http://example.com", nil) ctx := trace.ContextWithSpan( context.Background(), testSpan{sc: trace.NewSpanContext(tt.scc)}, ) b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { propagator.Inject(ctx, propagation.HeaderCarrier(req.Header)) } }) } } } func traceBenchmark(name string, b *testing.B, fn func(*testing.B)) { b.Run(name, func(b *testing.B) { b.ReportAllocs() fn(b) }) b.Run(name, func(b *testing.B) { b.ReportAllocs() fn(b) }) } open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/b3/b3_config.go000066400000000000000000000036161470323427300273040ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package b3 // import "go.opentelemetry.io/contrib/propagators/b3" type config struct { // InjectEncoding are the B3 encodings used when injecting trace // information. If no encoding is specified (i.e. `B3Unspecified`) // `B3SingleHeader` will be used as the default. InjectEncoding Encoding } // Option interface used for setting optional config properties. type Option interface { apply(*config) } type optionFunc func(*config) func (o optionFunc) apply(c *config) { o(c) } // newConfig creates a new config struct and applies opts to it. func newConfig(opts ...Option) *config { c := &config{} for _, opt := range opts { opt.apply(c) } return c } // Encoding is a bitmask representation of the B3 encoding type. type Encoding uint8 // supports returns if e has o bit(s) set. func (e Encoding) supports(o Encoding) bool { return e&o == o } const ( // B3Unspecified is an unspecified B3 encoding. B3Unspecified Encoding = 0 // B3MultipleHeader is a B3 encoding that uses multiple headers to // transmit tracing information all prefixed with `x-b3-`. // x-b3-traceid: {TraceId} // x-b3-parentspanid: {ParentSpanId} // x-b3-spanid: {SpanId} // x-b3-sampled: {SamplingState} // x-b3-flags: {DebugFlag} B3MultipleHeader Encoding = 1 << iota // B3SingleHeader is a B3 encoding that uses a single header named `b3` // to transmit tracing information. // b3: {TraceId}-{SpanId}-{SamplingState}-{ParentSpanId} B3SingleHeader ) // WithInjectEncoding sets the encoding the propagator will inject. // The encoding is interpreted as a bitmask. Therefore // // WithInjectEncoding(B3SingleHeader | B3MultipleHeader) // // means the propagator will inject both single and multi B3 headers. func WithInjectEncoding(encoding Encoding) Option { return optionFunc(func(c *config) { c.InjectEncoding = encoding }) } open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/b3/b3_data_test.go000066400000000000000000000550161470323427300300100ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package b3_test import ( "fmt" "go.opentelemetry.io/contrib/propagators/b3" "go.opentelemetry.io/otel/trace" ) const ( b3Context = "b3" b3Flags = "x-b3-flags" b3TraceID = "x-b3-traceid" b3SpanID = "x-b3-spanid" b3Sampled = "x-b3-sampled" b3ParentSpanID = "x-b3-parentspanid" ) const ( traceIDStr = "4bf92f3577b34da6a3ce929d0e0e4736" spanIDStr = "00f067aa0ba902b7" ) var ( traceID = mustTraceIDFromHex(traceIDStr) spanID = mustSpanIDFromHex(spanIDStr) traceID64bitPadded = mustTraceIDFromHex("0000000000000000a3ce929d0e0e4736") ) func mustTraceIDFromHex(s string) (t trace.TraceID) { var err error t, err = trace.TraceIDFromHex(s) if err != nil { panic(err) } return } func mustSpanIDFromHex(s string) (t trace.SpanID) { var err error t, err = trace.SpanIDFromHex(s) if err != nil { panic(err) } return } type extractTest struct { name string headers map[string]string wantScc trace.SpanContextConfig debug bool deferred bool } var extractHeaders = []extractTest{ { name: "empty", headers: map[string]string{}, wantScc: trace.SpanContextConfig{}, }, { name: "multiple: sampling state defer", headers: map[string]string{ b3TraceID: traceIDStr, b3SpanID: spanIDStr, }, wantScc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, }, deferred: true, }, { name: "multiple: sampling state deny", headers: map[string]string{ b3TraceID: traceIDStr, b3SpanID: spanIDStr, b3Sampled: "0", }, wantScc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, }, }, { name: "multiple: sampling state accept", headers: map[string]string{ b3TraceID: traceIDStr, b3SpanID: spanIDStr, b3Sampled: "1", }, wantScc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, }, { name: "multiple: sampling state as a boolean: true", headers: map[string]string{ b3TraceID: traceIDStr, b3SpanID: spanIDStr, b3Sampled: "true", }, wantScc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, }, { name: "multiple: sampling state as a boolean: false", headers: map[string]string{ b3TraceID: traceIDStr, b3SpanID: spanIDStr, b3Sampled: "false", }, wantScc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, }, }, { name: "multiple: debug flag set", headers: map[string]string{ b3TraceID: traceIDStr, b3SpanID: spanIDStr, b3Flags: "1", }, wantScc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, debug: true, }, { name: "multiple: debug flag set to not 1 (ignored)", headers: map[string]string{ b3TraceID: traceIDStr, b3SpanID: spanIDStr, b3Sampled: "1", b3Flags: "2", }, wantScc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, }, { // spec explicitly states "Debug implies an accept decision, so don't // also send the X-B3-Sampled header", make sure sampling is set in this case. name: "multiple: debug flag set and sampling state is deny", headers: map[string]string{ b3TraceID: traceIDStr, b3SpanID: spanIDStr, b3Sampled: "0", b3Flags: "1", }, wantScc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, debug: true, }, { name: "multiple: with parent span id", headers: map[string]string{ b3TraceID: traceIDStr, b3SpanID: spanIDStr, b3Sampled: "1", b3ParentSpanID: "00f067aa0ba90200", }, wantScc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, }, { name: "multiple: with only sampled state header", headers: map[string]string{ b3Sampled: "0", }, wantScc: trace.SpanContextConfig{}, }, { name: "multiple: left-padding 64-bit traceID", headers: map[string]string{ b3TraceID: "a3ce929d0e0e4736", b3SpanID: spanIDStr, }, wantScc: trace.SpanContextConfig{ TraceID: traceID64bitPadded, SpanID: spanID, }, deferred: true, }, { name: "single: sampling state defer", headers: map[string]string{ b3Context: fmt.Sprintf("%s-%s", traceIDStr, spanIDStr), }, wantScc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, }, deferred: true, }, { name: "single: sampling state deny", headers: map[string]string{ b3Context: fmt.Sprintf("%s-%s-0", traceIDStr, spanIDStr), }, wantScc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, }, }, { name: "single: sampling state accept", headers: map[string]string{ b3Context: fmt.Sprintf("%s-%s-1", traceIDStr, spanIDStr), }, wantScc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, }, { name: "single: sampling state debug", headers: map[string]string{ b3Context: fmt.Sprintf("%s-%s-d", traceIDStr, spanIDStr), }, wantScc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, debug: true, }, { name: "single: with parent span id", headers: map[string]string{ b3Context: fmt.Sprintf("%s-%s-1-00000000000000cd", traceIDStr, spanIDStr), }, wantScc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, }, { name: "single: with only sampling state deny", headers: map[string]string{ b3Context: "0", }, wantScc: trace.SpanContextConfig{}, }, { name: "single: left-padding 64-bit traceID", headers: map[string]string{ b3Context: fmt.Sprintf("a3ce929d0e0e4736-%s", spanIDStr), }, wantScc: trace.SpanContextConfig{ TraceID: traceID64bitPadded, SpanID: spanID, }, deferred: true, }, { name: "both single and multiple: single priority", headers: map[string]string{ b3Context: fmt.Sprintf("%s-%s-1", traceIDStr, spanIDStr), b3TraceID: traceIDStr, b3SpanID: spanIDStr, b3Sampled: "0", }, wantScc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, }, // An invalid Single Headers should fallback to multiple. { name: "both single and multiple: invalid single", headers: map[string]string{ b3Context: fmt.Sprintf("%s-%s-", traceIDStr, spanIDStr), b3TraceID: traceIDStr, b3SpanID: spanIDStr, b3Sampled: "0", }, wantScc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, }, }, // Invalid Mult Header should not be noticed as Single takes precedence. { name: "both single and multiple: invalid multiple", headers: map[string]string{ b3Context: fmt.Sprintf("%s-%s-1", traceIDStr, spanIDStr), b3TraceID: traceIDStr, b3SpanID: spanIDStr, b3Sampled: "invalid", }, wantScc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, }, } var extractInvalidHeaders = []extractTest{ { name: "multiple: trace ID length > 32", headers: map[string]string{ b3TraceID: "ab00000000000000000000000000000000", b3SpanID: "cd00000000000000", b3Sampled: "1", }, }, { name: "multiple: trace ID length >16 and <32", headers: map[string]string{ b3TraceID: "ab0000000000000000000000000000", b3SpanID: "cd00000000000000", b3Sampled: "1", }, }, { name: "multiple: trace ID length <16", headers: map[string]string{ b3TraceID: "ab0000000000", b3SpanID: "cd00000000000000", b3Sampled: "1", }, }, { name: "multiple: wrong span ID length", headers: map[string]string{ b3TraceID: "ab000000000000000000000000000000", b3SpanID: "cd0000000000000000", b3Sampled: "1", }, }, { name: "multiple: wrong sampled flag length", headers: map[string]string{ b3TraceID: "ab000000000000000000000000000000", b3SpanID: "cd00000000000000", b3Sampled: "10", }, }, { name: "multiple: bogus trace ID", headers: map[string]string{ b3TraceID: "qw000000000000000000000000000000", b3SpanID: "cd00000000000000", b3Sampled: "1", }, }, { name: "multiple: bogus span ID", headers: map[string]string{ b3TraceID: "ab000000000000000000000000000000", b3SpanID: "qw00000000000000", b3Sampled: "1", }, }, { name: "multiple: bogus sampled flag", headers: map[string]string{ b3TraceID: "ab000000000000000000000000000000", b3SpanID: "cd00000000000000", b3Sampled: "d", }, }, { name: "multiple: upper case trace ID", headers: map[string]string{ b3TraceID: "AB000000000000000000000000000000", b3SpanID: "cd00000000000000", b3Sampled: "1", }, }, { name: "multiple: upper case span ID", headers: map[string]string{ b3TraceID: "ab000000000000000000000000000000", b3SpanID: "CD00000000000000", b3Sampled: "1", }, }, { name: "multiple: zero trace ID", headers: map[string]string{ b3TraceID: "00000000000000000000000000000000", b3SpanID: "cd00000000000000", b3Sampled: "1", }, }, { name: "multiple: zero span ID", headers: map[string]string{ b3TraceID: "ab000000000000000000000000000000", b3SpanID: "0000000000000000", b3Sampled: "1", }, }, { name: "multiple: missing span ID", headers: map[string]string{ b3TraceID: "ab000000000000000000000000000000", b3Sampled: "1", }, }, { name: "multiple: missing trace ID", headers: map[string]string{ b3SpanID: "cd00000000000000", b3Sampled: "1", }, }, { name: "multiple: sampled header set to 1 but trace ID and span ID are missing", headers: map[string]string{ b3Sampled: "1", }, }, { name: "single: wrong trace ID length", headers: map[string]string{ b3Context: "ab00000000000000000000000000000000-cd00000000000000-1", }, }, { name: "single: wrong span ID length", headers: map[string]string{ b3Context: "ab000000000000000000000000000000-cd0000000000000000-1", }, }, { name: "single: wrong sampled state length", headers: map[string]string{ b3Context: "00-ab000000000000000000000000000000-cd00000000000000-01", }, }, { name: "single: wrong parent span ID length", headers: map[string]string{ b3Context: "ab000000000000000000000000000000-cd00000000000000-1-cd0000000000000000", }, }, { name: "single: bogus trace ID", headers: map[string]string{ b3Context: "qw000000000000000000000000000000-cd00000000000000-1", }, }, { name: "single: bogus span ID", headers: map[string]string{ b3Context: "ab000000000000000000000000000000-qw00000000000000-1", }, }, { name: "single: bogus sampled flag", headers: map[string]string{ b3Context: "ab000000000000000000000000000000-cd00000000000000-q", }, }, { name: "single: bogus parent span ID", headers: map[string]string{ b3Context: "ab000000000000000000000000000000-cd00000000000000-1-qw00000000000000", }, }, { name: "single: upper case trace ID", headers: map[string]string{ b3Context: "AB000000000000000000000000000000-cd00000000000000-1", }, }, { name: "single: upper case span ID", headers: map[string]string{ b3Context: "ab000000000000000000000000000000-CD00000000000000-1", }, }, { name: "single: upper case parent span ID", headers: map[string]string{ b3Context: "ab000000000000000000000000000000-cd00000000000000-1-EF00000000000000", }, }, { name: "single: zero trace ID and span ID", headers: map[string]string{ b3Context: "00000000000000000000000000000000-0000000000000000-1", }, }, { name: "single: with sampling set to true", headers: map[string]string{ b3Context: "ab000000000000000000000000000000-cd00000000000000-true", }, }, } type injectTest struct { name string encoding b3.Encoding scc trace.SpanContextConfig wantHeaders map[string]string doNotWantHeaders []string debug bool deferred bool } var injectHeader = []injectTest{ { name: "none: sampled", scc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, wantHeaders: map[string]string{ b3Context: fmt.Sprintf("%s-%s-%s", traceIDStr, spanIDStr, "1"), }, doNotWantHeaders: []string{ b3ParentSpanID, b3TraceID, b3SpanID, b3Sampled, b3Flags, b3Context, }, }, { name: "none: not sampled", scc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, }, wantHeaders: map[string]string{ b3Context: fmt.Sprintf("%s-%s-%s", traceIDStr, spanIDStr, "0"), }, doNotWantHeaders: []string{ b3ParentSpanID, b3TraceID, b3SpanID, b3Sampled, b3Flags, b3Context, }, }, { name: "none: unset sampled", scc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, }, wantHeaders: map[string]string{ b3Context: fmt.Sprintf("%s-%s", traceIDStr, spanIDStr), }, doNotWantHeaders: []string{ b3Sampled, b3TraceID, b3SpanID, b3ParentSpanID, b3Flags, b3Context, }, deferred: true, }, { name: "none: sampled only", scc: trace.SpanContextConfig{ TraceFlags: trace.FlagsSampled, }, wantHeaders: map[string]string{ b3Context: "1", }, doNotWantHeaders: []string{ b3TraceID, b3SpanID, b3ParentSpanID, b3Flags, b3Context, }, }, { name: "none: debug", scc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, }, wantHeaders: map[string]string{ b3Context: fmt.Sprintf("%s-%s-%s", traceIDStr, spanIDStr, "d"), }, doNotWantHeaders: []string{ b3Sampled, traceIDStr, spanIDStr, b3ParentSpanID, b3Context, }, debug: true, }, { name: "none: debug omitting sample", scc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, wantHeaders: map[string]string{ b3Context: fmt.Sprintf("%s-%s-%s", traceIDStr, spanIDStr, "d"), }, doNotWantHeaders: []string{ b3Sampled, traceIDStr, spanIDStr, b3Flags, b3ParentSpanID, b3Context, }, debug: true, }, { name: "multiple: sampled", encoding: b3.B3MultipleHeader, scc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, wantHeaders: map[string]string{ b3TraceID: traceIDStr, b3SpanID: spanIDStr, b3Sampled: "1", }, doNotWantHeaders: []string{ b3ParentSpanID, b3Flags, b3Context, }, }, { name: "multiple: not sampled", encoding: b3.B3MultipleHeader, scc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, }, wantHeaders: map[string]string{ b3TraceID: traceIDStr, b3SpanID: spanIDStr, b3Sampled: "0", }, doNotWantHeaders: []string{ b3ParentSpanID, b3Flags, b3Context, }, }, { name: "multiple: unset sampled", encoding: b3.B3MultipleHeader, scc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, }, wantHeaders: map[string]string{ b3TraceID: traceIDStr, b3SpanID: spanIDStr, }, doNotWantHeaders: []string{ b3Sampled, b3ParentSpanID, b3Flags, b3Context, }, deferred: true, }, { name: "multiple: sampled only", encoding: b3.B3MultipleHeader, scc: trace.SpanContextConfig{ TraceFlags: trace.FlagsSampled, }, wantHeaders: map[string]string{ b3Sampled: "1", }, doNotWantHeaders: []string{ b3TraceID, b3SpanID, b3ParentSpanID, b3Flags, b3Context, }, }, { name: "multiple: debug", encoding: b3.B3MultipleHeader, scc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, }, wantHeaders: map[string]string{ b3TraceID: traceIDStr, b3SpanID: spanIDStr, b3Flags: "1", }, doNotWantHeaders: []string{ b3Sampled, b3ParentSpanID, b3Context, }, debug: true, }, { name: "multiple: debug omitting sample", encoding: b3.B3MultipleHeader, scc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, wantHeaders: map[string]string{ b3TraceID: traceIDStr, b3SpanID: spanIDStr, b3Flags: "1", }, doNotWantHeaders: []string{ b3Sampled, b3ParentSpanID, b3Context, }, debug: true, }, { name: "single: sampled", encoding: b3.B3SingleHeader, scc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, wantHeaders: map[string]string{ b3Context: fmt.Sprintf("%s-%s-1", traceIDStr, spanIDStr), }, doNotWantHeaders: []string{ b3TraceID, b3SpanID, b3Sampled, b3ParentSpanID, b3Flags, }, }, { name: "single: not sampled", encoding: b3.B3SingleHeader, scc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, }, wantHeaders: map[string]string{ b3Context: fmt.Sprintf("%s-%s-0", traceIDStr, spanIDStr), }, doNotWantHeaders: []string{ b3TraceID, b3SpanID, b3Sampled, b3ParentSpanID, b3Flags, }, }, { name: "single: unset sampled", encoding: b3.B3SingleHeader, scc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, }, wantHeaders: map[string]string{ b3Context: fmt.Sprintf("%s-%s", traceIDStr, spanIDStr), }, doNotWantHeaders: []string{ b3TraceID, b3SpanID, b3Sampled, b3ParentSpanID, b3Flags, }, deferred: true, }, { name: "single: sampled only", encoding: b3.B3SingleHeader, scc: trace.SpanContextConfig{ TraceFlags: trace.FlagsSampled, }, wantHeaders: map[string]string{ b3Context: "1", }, doNotWantHeaders: []string{ b3Sampled, b3TraceID, b3SpanID, b3ParentSpanID, b3Flags, b3Context, }, }, { name: "single: debug", encoding: b3.B3SingleHeader, scc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, }, wantHeaders: map[string]string{ b3Context: fmt.Sprintf("%s-%s-d", traceIDStr, spanIDStr), }, doNotWantHeaders: []string{ b3TraceID, b3SpanID, b3Flags, b3Sampled, b3ParentSpanID, b3Context, }, debug: true, }, { name: "single: debug omitting sample", encoding: b3.B3SingleHeader, scc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, wantHeaders: map[string]string{ b3Context: fmt.Sprintf("%s-%s-d", traceIDStr, spanIDStr), }, doNotWantHeaders: []string{ b3TraceID, b3SpanID, b3Flags, b3Sampled, b3ParentSpanID, b3Context, }, debug: true, }, { name: "single+multiple: sampled", encoding: b3.B3SingleHeader | b3.B3MultipleHeader, scc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, wantHeaders: map[string]string{ b3TraceID: traceIDStr, b3SpanID: spanIDStr, b3Sampled: "1", b3Context: fmt.Sprintf("%s-%s-1", traceIDStr, spanIDStr), }, doNotWantHeaders: []string{ b3ParentSpanID, b3Flags, }, }, { name: "single+multiple: not sampled", encoding: b3.B3SingleHeader | b3.B3MultipleHeader, scc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, }, wantHeaders: map[string]string{ b3TraceID: traceIDStr, b3SpanID: spanIDStr, b3Sampled: "0", b3Context: fmt.Sprintf("%s-%s-0", traceIDStr, spanIDStr), }, doNotWantHeaders: []string{ b3ParentSpanID, b3Flags, }, }, { name: "single+multiple: unset sampled", encoding: b3.B3SingleHeader | b3.B3MultipleHeader, scc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, }, wantHeaders: map[string]string{ b3TraceID: traceIDStr, b3SpanID: spanIDStr, b3Context: fmt.Sprintf("%s-%s", traceIDStr, spanIDStr), }, doNotWantHeaders: []string{ b3Sampled, b3ParentSpanID, b3Flags, }, deferred: true, }, { name: "single+multiple: sampled only", encoding: b3.B3SingleHeader | b3.B3MultipleHeader, scc: trace.SpanContextConfig{ TraceFlags: trace.FlagsSampled, }, wantHeaders: map[string]string{ b3Context: "1", b3Sampled: "1", }, doNotWantHeaders: []string{ b3TraceID, b3SpanID, b3ParentSpanID, b3Flags, }, }, { name: "single+multiple: debug", encoding: b3.B3SingleHeader | b3.B3MultipleHeader, scc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, }, wantHeaders: map[string]string{ b3TraceID: traceIDStr, b3SpanID: spanIDStr, b3Flags: "1", b3Context: fmt.Sprintf("%s-%s-d", traceIDStr, spanIDStr), }, doNotWantHeaders: []string{ b3Sampled, b3ParentSpanID, }, debug: true, }, { name: "single+multiple: debug omitting sample", encoding: b3.B3SingleHeader | b3.B3MultipleHeader, scc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, wantHeaders: map[string]string{ b3TraceID: traceIDStr, b3SpanID: spanIDStr, b3Flags: "1", b3Context: fmt.Sprintf("%s-%s-d", traceIDStr, spanIDStr), }, doNotWantHeaders: []string{ b3Sampled, b3ParentSpanID, }, debug: true, }, } var injectInvalidHeaderGenerator = []injectTest{ { name: "empty", scc: trace.SpanContextConfig{}, }, { name: "missing traceID", scc: trace.SpanContextConfig{ SpanID: spanID, TraceFlags: trace.FlagsSampled, }, }, { name: "missing spanID", scc: trace.SpanContextConfig{ TraceID: traceID, TraceFlags: trace.FlagsSampled, }, }, { name: "missing traceID and spanID", scc: trace.SpanContextConfig{ TraceFlags: trace.FlagsSampled, }, }, } var injectInvalidHeader []injectTest func init() { // Perform a test for each invalid injectTest with all combinations of // encoding values. injectInvalidHeader = make([]injectTest, 0, len(injectInvalidHeaderGenerator)*4) allHeaders := []string{ b3TraceID, b3SpanID, b3Sampled, b3ParentSpanID, b3Flags, b3Context, } // Nothing should be set for any header regardless of encoding. for _, t := range injectInvalidHeaderGenerator { injectInvalidHeader = append(injectInvalidHeader, injectTest{ name: "none: " + t.name, scc: t.scc, doNotWantHeaders: allHeaders, }) injectInvalidHeader = append(injectInvalidHeader, injectTest{ name: "multiple: " + t.name, encoding: b3.B3MultipleHeader, scc: t.scc, doNotWantHeaders: allHeaders, }) injectInvalidHeader = append(injectInvalidHeader, injectTest{ name: "single: " + t.name, encoding: b3.B3SingleHeader, scc: t.scc, doNotWantHeaders: allHeaders, }) injectInvalidHeader = append(injectInvalidHeader, injectTest{ name: "single+multiple: " + t.name, encoding: b3.B3SingleHeader | b3.B3MultipleHeader, scc: t.scc, doNotWantHeaders: allHeaders, }) } } open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/b3/b3_example_test.go000066400000000000000000000010461470323427300305240ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package b3_test import ( "go.opentelemetry.io/contrib/propagators/b3" "go.opentelemetry.io/otel" ) func ExampleNew() { p := b3.New() // Register the B3 propagator globally. otel.SetTextMapPropagator(p) } func ExampleNew_injectEncoding() { // Create a B3 propagator configured to inject context with both multiple // and single header B3 HTTP encoding. p := b3.New(b3.WithInjectEncoding(b3.B3MultipleHeader | b3.B3SingleHeader)) otel.SetTextMapPropagator(p) } open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/b3/b3_integration_test.go000066400000000000000000000074701470323427300314230ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package b3_test import ( "context" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/google/go-cmp/cmp" "go.opentelemetry.io/contrib/propagators/b3" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" ) func TestExtractB3(t *testing.T) { testGroup := []struct { name string tests []extractTest }{ { name: "valid extract headers", tests: extractHeaders, }, { name: "invalid extract headers", tests: extractInvalidHeaders, }, } for _, tg := range testGroup { propagator := b3.New() for _, tt := range tg.tests { t.Run(tt.name, func(t *testing.T) { header := make(http.Header, len(tt.headers)) for h, v := range tt.headers { header.Set(h, v) } ctx := context.Background() ctx = propagator.Extract(ctx, propagation.HeaderCarrier(header)) gotSc := trace.SpanContextFromContext(ctx) comparer := cmp.Comparer(func(a, b trace.SpanContext) bool { // Do not compare remote field, it is unset on empty // SpanContext. newA := a.WithRemote(b.IsRemote()) return newA.Equal(b) }) if diff := cmp.Diff(gotSc, trace.NewSpanContext(tt.wantScc), comparer); diff != "" { t.Errorf("%s: %s: -got +want %s", tg.name, tt.name, diff) } assert.Equal(t, tt.debug, b3.DebugFromContext(ctx)) assert.Equal(t, tt.deferred, b3.DeferredFromContext(ctx)) }) } } } type testSpan struct { trace.Span sc trace.SpanContext } func (s testSpan) SpanContext() trace.SpanContext { return s.sc } func TestInjectB3(t *testing.T) { testGroup := []struct { name string tests []injectTest }{ { name: "valid inject headers", tests: injectHeader, }, { name: "invalid inject headers", tests: injectInvalidHeader, }, } for _, tg := range testGroup { for _, tt := range tg.tests { propagator := b3.New(b3.WithInjectEncoding(tt.encoding)) t.Run(tt.name, func(t *testing.T) { header := http.Header{} ctx := trace.ContextWithSpanContext( context.Background(), trace.NewSpanContext(tt.scc), ) ctx = b3.WithDebug(ctx, tt.debug) ctx = b3.WithDeferred(ctx, tt.deferred) propagator.Inject(ctx, propagation.HeaderCarrier(header)) for h, v := range tt.wantHeaders { got, want := header.Get(h), v if diff := cmp.Diff(got, want); diff != "" { t.Errorf("%s: %s, header=%s: -got +want %s", tg.name, tt.name, h, diff) } } for _, h := range tt.doNotWantHeaders { v, gotOk := header[h] if diff := cmp.Diff(gotOk, false); diff != "" { t.Errorf("%s: %s, header=%s: -got +want %s, value=%s", tg.name, tt.name, h, diff, v) } } }) } } } func TestB3Propagator_Fields(t *testing.T) { tests := []struct { name string propagator propagation.TextMapPropagator want []string }{ { name: "no encoding specified", propagator: b3.New(), want: []string{ b3TraceID, b3SpanID, b3Sampled, b3Flags, }, }, { name: "B3MultipleHeader encoding specified", propagator: b3.New(b3.WithInjectEncoding(b3.B3MultipleHeader)), want: []string{ b3TraceID, b3SpanID, b3Sampled, b3Flags, }, }, { name: "B3SingleHeader encoding specified", propagator: b3.New(b3.WithInjectEncoding(b3.B3SingleHeader)), want: []string{ b3Context, }, }, { name: "B3SingleHeader and B3MultipleHeader encoding specified", propagator: b3.New(b3.WithInjectEncoding(b3.B3SingleHeader | b3.B3MultipleHeader)), want: []string{ b3Context, b3TraceID, b3SpanID, b3Sampled, b3Flags, }, }, } for _, test := range tests { if diff := cmp.Diff(test.propagator.Fields(), test.want); diff != "" { t.Errorf("%s: Fields: -got +want %s", test.name, diff) } } } open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/b3/b3_propagator.go000066400000000000000000000251201470323427300302070ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package b3 // import "go.opentelemetry.io/contrib/propagators/b3" import ( "context" "errors" "strings" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" ) const ( // Default B3 Header names. b3ContextHeader = "b3" b3DebugFlagHeader = "x-b3-flags" b3TraceIDHeader = "x-b3-traceid" b3SpanIDHeader = "x-b3-spanid" b3SampledHeader = "x-b3-sampled" b3ParentSpanIDHeader = "x-b3-parentspanid" b3TraceIDPadding = "0000000000000000" // B3 Single Header encoding widths. separatorWidth = 1 // Single "-" character. samplingWidth = 1 // Single hex character. traceID64BitsWidth = 64 / 4 // 16 hex character Trace ID. traceID128BitsWidth = 128 / 4 // 32 hex character Trace ID. spanIDWidth = 16 // 16 hex character ID. parentSpanIDWidth = 16 // 16 hex character ID. ) var ( empty = trace.SpanContext{} errInvalidSampledByte = errors.New("invalid B3 Sampled found") errInvalidSampledHeader = errors.New("invalid B3 Sampled header found") errInvalidTraceIDHeader = errors.New("invalid B3 traceID header found") errInvalidSpanIDHeader = errors.New("invalid B3 spanID header found") errInvalidParentSpanIDHeader = errors.New("invalid B3 ParentSpanID header found") errInvalidScope = errors.New("require either both traceID and spanID or none") errInvalidScopeParent = errors.New("traceID and spanID required for ParentSpanID") errInvalidScopeParentSingle = errors.New("traceID, spanID and Sampled required for ParentSpanID") errEmptyContext = errors.New("empty request context") errInvalidTraceIDValue = errors.New("invalid B3 traceID value found") errInvalidSpanIDValue = errors.New("invalid B3 spanID value found") errInvalidParentSpanIDValue = errors.New("invalid B3 ParentSpanID value found") ) type propagator struct { cfg config } var _ propagation.TextMapPropagator = propagator{} // New creates a B3 implementation of propagation.TextMapPropagator. // B3 propagator serializes SpanContext to/from B3 Headers. // This propagator supports both versions of B3 headers, // 1. Single Header: // b3: {TraceId}-{SpanId}-{SamplingState}-{ParentSpanId} // 2. Multiple Headers: // x-b3-traceid: {TraceId} // x-b3-parentspanid: {ParentSpanId} // x-b3-spanid: {SpanId} // x-b3-sampled: {SamplingState} // x-b3-flags: {DebugFlag} // // The Single Header propagator is used by default. func New(opts ...Option) propagation.TextMapPropagator { cfg := newConfig(opts...) return propagator{ cfg: *cfg, } } // Inject injects a context into the carrier as B3 headers. // The parent span ID is omitted because it is not tracked in the // SpanContext. func (b3 propagator) Inject(ctx context.Context, carrier propagation.TextMapCarrier) { sc := trace.SpanFromContext(ctx).SpanContext() if b3.cfg.InjectEncoding.supports(B3SingleHeader) || b3.cfg.InjectEncoding == B3Unspecified { header := []string{} if sc.TraceID().IsValid() && sc.SpanID().IsValid() { header = append(header, sc.TraceID().String(), sc.SpanID().String()) } if debugFromContext(ctx) { header = append(header, "d") } else if !(deferredFromContext(ctx)) { if sc.IsSampled() { header = append(header, "1") } else { header = append(header, "0") } } carrier.Set(b3ContextHeader, strings.Join(header, "-")) } if b3.cfg.InjectEncoding.supports(B3MultipleHeader) { if sc.TraceID().IsValid() && sc.SpanID().IsValid() { carrier.Set(b3TraceIDHeader, sc.TraceID().String()) carrier.Set(b3SpanIDHeader, sc.SpanID().String()) } if debugFromContext(ctx) { // Since Debug implies deferred, don't also send "X-B3-Sampled". carrier.Set(b3DebugFlagHeader, "1") } else if !(deferredFromContext(ctx)) { if sc.IsSampled() { carrier.Set(b3SampledHeader, "1") } else { carrier.Set(b3SampledHeader, "0") } } } } // Extract extracts a context from the carrier if it contains B3 headers. func (b3 propagator) Extract(ctx context.Context, carrier propagation.TextMapCarrier) context.Context { var ( sc trace.SpanContext err error ) // Default to Single Header if a valid value exists. if h := carrier.Get(b3ContextHeader); h != "" { ctx, sc, err = extractSingle(ctx, h) if err == nil && sc.IsValid() { return trace.ContextWithRemoteSpanContext(ctx, sc) } // The Single Header value was invalid, fallback to Multiple Header. } var ( traceID = carrier.Get(b3TraceIDHeader) spanID = carrier.Get(b3SpanIDHeader) parentSpanID = carrier.Get(b3ParentSpanIDHeader) sampled = carrier.Get(b3SampledHeader) debugFlag = carrier.Get(b3DebugFlagHeader) ) ctx, sc, err = extractMultiple(ctx, traceID, spanID, parentSpanID, sampled, debugFlag) if err != nil || !sc.IsValid() { // clear the deferred flag if we don't have a valid SpanContext return withDeferred(ctx, false) } return trace.ContextWithRemoteSpanContext(ctx, sc) } func (b3 propagator) Fields() []string { header := []string{} if b3.cfg.InjectEncoding.supports(B3SingleHeader) { header = append(header, b3ContextHeader) } if b3.cfg.InjectEncoding.supports(B3MultipleHeader) || b3.cfg.InjectEncoding == B3Unspecified { header = append(header, b3TraceIDHeader, b3SpanIDHeader, b3SampledHeader, b3DebugFlagHeader) } return header } // extractMultiple reconstructs a SpanContext from header values based on B3 // Multiple header. It is based on the implementation found here: // https://github.com/openzipkin/zipkin-go/blob/v0.2.2/propagation/b3/spancontext.go // and adapted to support a SpanContext. func extractMultiple(ctx context.Context, traceID, spanID, parentSpanID, sampled, flags string) (context.Context, trace.SpanContext, error) { var ( err error requiredCount int scc = trace.SpanContextConfig{} ) // correct values for an existing sampled header are "0" and "1". // For legacy support and being lenient to other tracing implementations we // allow "true" and "false" as inputs for interop purposes. switch strings.ToLower(sampled) { case "0", "false": // Zero value for TraceFlags sample bit is unset. case "1", "true": scc.TraceFlags = trace.FlagsSampled case "": ctx = withDeferred(ctx, true) default: return ctx, empty, errInvalidSampledHeader } // The only accepted value for Flags is "1". This will set Debug bitmask and // sampled bitmask to 1 since debug implicitly means sampled. All other // values and omission of header will be ignored. According to the spec. User // shouldn't send X-B3-Sampled header along with X-B3-Flags header. Thus we will // ignore X-B3-Sampled header when X-B3-Flags header is sent and valid. if flags == "1" { ctx = withDeferred(ctx, false) ctx = withDebug(ctx, true) scc.TraceFlags |= trace.FlagsSampled } if traceID != "" { requiredCount++ id := traceID if len(traceID) == 16 { // Pad 64-bit trace IDs. id = b3TraceIDPadding + traceID } if scc.TraceID, err = trace.TraceIDFromHex(id); err != nil { return ctx, empty, errInvalidTraceIDHeader } } if spanID != "" { requiredCount++ if scc.SpanID, err = trace.SpanIDFromHex(spanID); err != nil { return ctx, empty, errInvalidSpanIDHeader } } if requiredCount != 0 && requiredCount != 2 { return ctx, empty, errInvalidScope } if parentSpanID != "" { if requiredCount == 0 { return ctx, empty, errInvalidScopeParent } // Validate parent span ID but we do not use it so do not save it. if _, err = trace.SpanIDFromHex(parentSpanID); err != nil { return ctx, empty, errInvalidParentSpanIDHeader } } return ctx, trace.NewSpanContext(scc), nil } // extractSingle reconstructs a SpanContext from contextHeader based on a B3 // Single header. It is based on the implementation found here: // https://github.com/openzipkin/zipkin-go/blob/v0.2.2/propagation/b3/spancontext.go // and adapted to support a SpanContext. func extractSingle(ctx context.Context, contextHeader string) (context.Context, trace.SpanContext, error) { if contextHeader == "" { return ctx, empty, errEmptyContext } var ( scc = trace.SpanContextConfig{} sampling string ) headerLen := len(contextHeader) switch { case headerLen == samplingWidth: sampling = contextHeader case headerLen == traceID64BitsWidth || headerLen == traceID128BitsWidth: // Trace ID by itself is invalid. return ctx, empty, errInvalidScope case headerLen >= traceID64BitsWidth+spanIDWidth+separatorWidth: pos := 0 var traceID string switch { case string(contextHeader[traceID64BitsWidth]) == "-": // traceID must be 64 bits pos += traceID64BitsWidth // {traceID} traceID = b3TraceIDPadding + contextHeader[0:pos] case string(contextHeader[32]) == "-": // traceID must be 128 bits pos += traceID128BitsWidth // {traceID} traceID = contextHeader[0:pos] default: return ctx, empty, errInvalidTraceIDValue } var err error scc.TraceID, err = trace.TraceIDFromHex(traceID) if err != nil { return ctx, empty, errInvalidTraceIDValue } pos += separatorWidth // {traceID}- if headerLen < pos+spanIDWidth { return ctx, empty, errInvalidSpanIDValue } scc.SpanID, err = trace.SpanIDFromHex(contextHeader[pos : pos+spanIDWidth]) if err != nil { return ctx, empty, errInvalidSpanIDValue } pos += spanIDWidth // {traceID}-{spanID} if headerLen > pos { if headerLen == pos+separatorWidth { // {traceID}-{spanID}- is invalid. return ctx, empty, errInvalidSampledByte } pos += separatorWidth // {traceID}-{spanID}- switch { case headerLen == pos+samplingWidth: sampling = string(contextHeader[pos]) case headerLen == pos+parentSpanIDWidth: // {traceID}-{spanID}-{parentSpanID} is invalid. return ctx, empty, errInvalidScopeParentSingle case headerLen == pos+samplingWidth+separatorWidth+parentSpanIDWidth: sampling = string(contextHeader[pos]) pos += samplingWidth + separatorWidth // {traceID}-{spanID}-{sampling}- // Validate parent span ID but we do not use it so do not // save it. _, err = trace.SpanIDFromHex(contextHeader[pos:]) if err != nil { return ctx, empty, errInvalidParentSpanIDValue } default: return ctx, empty, errInvalidParentSpanIDValue } } default: return ctx, empty, errInvalidTraceIDValue } switch sampling { case "": ctx = withDeferred(ctx, true) case "d": ctx = withDebug(ctx, true) scc.TraceFlags = trace.FlagsSampled case "1": scc.TraceFlags = trace.FlagsSampled case "0": // Zero value for TraceFlags sample bit is unset. default: return ctx, empty, errInvalidSampledByte } return ctx, trace.NewSpanContext(scc), nil } open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/b3/b3_propagator_test.go000066400000000000000000000220761470323427300312550ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package b3 import ( "context" "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/trace" ) var ( traceID = trace.TraceID{0, 0, 0, 0, 0, 0, 0, 0x7b, 0, 0, 0, 0, 0, 0, 0x1, 0xc8} traceIDStr = "000000000000007b00000000000001c8" spanID = trace.SpanID{0, 0, 0, 0, 0, 0, 0, 0x7b} spanIDStr = "000000000000007b" ) func TestExtractMultiple(t *testing.T) { tests := []struct { traceID string spanID string parentSpanID string sampled string flags string expected trace.SpanContextConfig err error debug bool deferred bool }{ { "", "", "", "0", "", trace.SpanContextConfig{}, nil, false, false, }, { "", "", "", "", "", trace.SpanContextConfig{}, nil, false, true, }, { "", "", "", "1", "", trace.SpanContextConfig{TraceFlags: trace.FlagsSampled}, nil, false, false, }, { "", "", "", "", "1", trace.SpanContextConfig{TraceFlags: trace.FlagsSampled}, nil, true, false, }, { "", "", "", "0", "1", trace.SpanContextConfig{TraceFlags: trace.FlagsSampled}, nil, true, false, }, { "", "", "", "1", "1", trace.SpanContextConfig{TraceFlags: trace.FlagsSampled}, nil, true, false, }, { traceIDStr, spanIDStr, "", "", "", trace.SpanContextConfig{TraceID: traceID, SpanID: spanID}, nil, false, true, }, { traceIDStr, spanIDStr, "", "0", "", trace.SpanContextConfig{TraceID: traceID, SpanID: spanID}, nil, false, false, }, // Ensure backwards compatibility. { traceIDStr, spanIDStr, "", "false", "", trace.SpanContextConfig{TraceID: traceID, SpanID: spanID}, nil, false, false, }, { traceIDStr, spanIDStr, "", "1", "", trace.SpanContextConfig{TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled}, nil, false, false, }, // Ensure backwards compatibility. { traceIDStr, spanIDStr, "", "true", "", trace.SpanContextConfig{TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled}, nil, false, false, }, { traceIDStr, spanIDStr, "", "a", "", trace.SpanContextConfig{}, errInvalidSampledHeader, false, false, }, { traceIDStr, spanIDStr, "", "1", "1", trace.SpanContextConfig{TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled}, nil, true, false, }, // Invalid flags are discarded. { traceIDStr, spanIDStr, "", "1", "invalid", trace.SpanContextConfig{TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled}, nil, false, false, }, // Support short trace IDs. { "00000000000001c8", spanIDStr, "", "0", "", trace.SpanContextConfig{ TraceID: trace.TraceID{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x1, 0xc8}, SpanID: spanID, }, nil, false, false, }, { "00000000000001c", spanIDStr, "", "0", "", trace.SpanContextConfig{}, errInvalidTraceIDHeader, false, false, }, { "00000000000001c80", spanIDStr, "", "0", "", trace.SpanContextConfig{}, errInvalidTraceIDHeader, false, false, }, { traceIDStr[:len(traceIDStr)-2], spanIDStr, "", "0", "", trace.SpanContextConfig{}, errInvalidTraceIDHeader, false, false, }, { traceIDStr + "0", spanIDStr, "", "0", "", trace.SpanContextConfig{}, errInvalidTraceIDHeader, false, false, }, { traceIDStr, "00000000000001c", "", "0", "", trace.SpanContextConfig{}, errInvalidSpanIDHeader, false, false, }, { traceIDStr, "00000000000001c80", "", "0", "", trace.SpanContextConfig{}, errInvalidSpanIDHeader, false, false, }, { traceIDStr, "", "", "0", "", trace.SpanContextConfig{}, errInvalidScope, false, false, }, { "", spanIDStr, "", "0", "", trace.SpanContextConfig{}, errInvalidScope, false, false, }, { "", "", spanIDStr, "0", "", trace.SpanContextConfig{}, errInvalidScopeParent, false, false, }, { traceIDStr, spanIDStr, "00000000000001c8", "0", "", trace.SpanContextConfig{TraceID: traceID, SpanID: spanID}, nil, false, false, }, { traceIDStr, spanIDStr, "00000000000001c", "0", "", trace.SpanContextConfig{}, errInvalidParentSpanIDHeader, false, false, }, { traceIDStr, spanIDStr, "00000000000001c80", "0", "", trace.SpanContextConfig{}, errInvalidParentSpanIDHeader, false, false, }, } for _, test := range tests { ctx, actual, err := extractMultiple( context.Background(), test.traceID, test.spanID, test.parentSpanID, test.sampled, test.flags, ) info := []interface{}{ "trace ID: %q, span ID: %q, parent span ID: %q, sampled: %q, flags: %q", test.traceID, test.spanID, test.parentSpanID, test.sampled, test.flags, } if !assert.Equal(t, test.err, err, info...) { continue } assert.Equal(t, trace.NewSpanContext(test.expected), actual, info...) assert.Equal(t, debugFromContext(ctx), test.debug, info...) assert.Equal(t, deferredFromContext(ctx), test.deferred, info...) } } func TestExtractSingle(t *testing.T) { tests := []struct { header string expected trace.SpanContextConfig err error debug bool deferred bool }{ {"0", trace.SpanContextConfig{}, nil, false, false}, {"1", trace.SpanContextConfig{TraceFlags: trace.FlagsSampled}, nil, false, false}, {"d", trace.SpanContextConfig{TraceFlags: trace.FlagsSampled}, nil, true, false}, {"a", trace.SpanContextConfig{}, errInvalidSampledByte, false, false}, {"3", trace.SpanContextConfig{}, errInvalidSampledByte, false, false}, {"000000000000007b", trace.SpanContextConfig{}, errInvalidScope, false, false}, {"000000000000007b00000000000001c8", trace.SpanContextConfig{}, errInvalidScope, false, false}, // TraceID with illegal length { "000001c8-000000000000007b", trace.SpanContextConfig{}, errInvalidTraceIDValue, false, false, }, // SpanID with illegal length { "000000000000007b00000000000001c8-0000007b", trace.SpanContextConfig{}, errInvalidSpanIDValue, false, false, }, // Support short trace IDs. { "00000000000001c8-000000000000007b", trace.SpanContextConfig{ TraceID: trace.TraceID{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x1, 0xc8}, SpanID: spanID, }, nil, false, true, }, { "000000000000007b00000000000001c8-000000000000007b", trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, }, nil, false, true, }, { "000000000000007b00000000000001c8-000000000000007b-", trace.SpanContextConfig{}, errInvalidSampledByte, false, false, }, { "000000000000007b00000000000001c8-000000000000007b-3", trace.SpanContextConfig{}, errInvalidSampledByte, false, false, }, { "000000000000007b00000000000001c8-000000000000007b-00000000000001c8", trace.SpanContextConfig{}, errInvalidScopeParentSingle, false, false, }, { "000000000000007b00000000000001c8-000000000000007b-1", trace.SpanContextConfig{TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled}, nil, false, false, }, // ParentSpanID is discarded, but should still result in a parsable header. { "000000000000007b00000000000001c8-000000000000007b-1-00000000000001c8", trace.SpanContextConfig{TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled}, nil, false, false, }, { "000000000000007b00000000000001c8-000000000000007b-1-00000000000001c", trace.SpanContextConfig{}, errInvalidParentSpanIDValue, false, false, }, {"", trace.SpanContextConfig{}, errEmptyContext, false, false}, } for _, test := range tests { ctx, actual, err := extractSingle(context.Background(), test.header) if !assert.Equal(t, test.err, err, "header: %s", test.header) { continue } assert.Equal(t, trace.NewSpanContext(test.expected), actual, "header: %s", test.header) assert.Equal(t, debugFromContext(ctx), test.debug) assert.Equal(t, deferredFromContext(ctx), test.deferred) } } func TestB3EncodingOperations(t *testing.T) { encodings := []Encoding{ B3MultipleHeader, B3SingleHeader, B3Unspecified, } // Test for overflow (or something really unexpected). for i, e := range encodings { for j := i + 1; j < i+len(encodings); j++ { o := encodings[j%len(encodings)] assert.NotEqual(t, e, o, "%v == %v", e, o) } } // B3Unspecified is a special case, it supports only itself, but is // supported by everything. assert.True(t, B3Unspecified.supports(B3Unspecified)) for _, e := range encodings[:len(encodings)-1] { assert.False(t, B3Unspecified.supports(e), e) assert.True(t, e.supports(B3Unspecified), e) } // Skip the special case for B3Unspecified. for i, e := range encodings[:len(encodings)-1] { // Everything should support itself. assert.True(t, e.supports(e)) for j := i + 1; j < i+len(encodings); j++ { o := encodings[j%len(encodings)] // Any "or" combination should be supportive of an operand. assert.True(t, (e | o).supports(e), "(%[0]v|%[1]v).supports(%[0]v)", e, o) // Bitmasks should be unique. assert.False(t, o.supports(e), "%v.supports(%v)", o, e) } } // Encoding.supports should be more inclusive than equality. all := ^B3Unspecified for _, e := range encodings { assert.True(t, all.supports(e)) } } open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/b3/context.go000066400000000000000000000023521470323427300271330ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package b3 // import "go.opentelemetry.io/contrib/propagators/b3" import "context" type b3KeyType int const ( debugKey b3KeyType = iota deferredKey ) // withDebug returns a copy of parent with debug set as the debug flag value . func withDebug(parent context.Context, debug bool) context.Context { return context.WithValue(parent, debugKey, debug) } // debugFromContext returns the debug value stored in ctx. // // If no debug value is stored in ctx false is returned. func debugFromContext(ctx context.Context) bool { if ctx == nil { return false } if debug, ok := ctx.Value(debugKey).(bool); ok { return debug } return false } // withDeferred returns a copy of parent with deferred set as the deferred flag value . func withDeferred(parent context.Context, deferred bool) context.Context { return context.WithValue(parent, deferredKey, deferred) } // deferredFromContext returns the deferred value stored in ctx. // // If no deferred value is stored in ctx false is returned. func deferredFromContext(ctx context.Context) bool { if ctx == nil { return false } if deferred, ok := ctx.Value(deferredKey).(bool); ok { return deferred } return false } open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/b3/context_test.go000066400000000000000000000003661470323427300301750ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package b3 var ( WithDebug = withDebug DebugFromContext = debugFromContext WithDeferred = withDeferred DeferredFromContext = deferredFromContext ) open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/b3/doc.go000066400000000000000000000004101470323427300262050ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package b3 implements the B3 propagator specification as defined at // https://github.com/openzipkin/b3-propagation package b3 // import "go.opentelemetry.io/contrib/propagators/b3" open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/b3/go.mod000066400000000000000000000007671470323427300262360ustar00rootroot00000000000000module go.opentelemetry.io/contrib/propagators/b3 go 1.22 require ( github.com/google/go-cmp v0.6.0 github.com/stretchr/testify v1.9.0 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/trace v1.31.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/b3/go.sum000066400000000000000000000037151470323427300262570ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/b3/version.go000066400000000000000000000007541470323427300271400ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package b3 // import "go.opentelemetry.io/contrib/propagators/b3" // Version is the current release version of the B3 propagator. func Version() string { return "1.31.0" // This string is updated by the pre_release.sh script during release } // SemVersion is the semantic version to be supplied to tracer/meter creation. // // Deprecated: Use [Version] instead. func SemVersion() string { return Version() } open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/jaeger/000077500000000000000000000000001470323427300260475ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/jaeger/context.go000066400000000000000000000013301470323427300300570ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package jaeger // import "go.opentelemetry.io/contrib/propagators/jaeger" import "context" type jaegerKeyType int const ( debugKey jaegerKeyType = iota ) // withDebug returns a copy of parent with debug set as the debug flag value . func withDebug(parent context.Context, debug bool) context.Context { return context.WithValue(parent, debugKey, debug) } // debugFromContext returns the debug value stored in ctx. // // If no debug value is stored in ctx false is returned. func debugFromContext(ctx context.Context) bool { if ctx == nil { return false } if debug, ok := ctx.Value(debugKey).(bool); ok { return debug } return false } open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/jaeger/context_test.go000066400000000000000000000002521470323427300311200ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package jaeger var ( WithDebug = withDebug DebugFromContext = debugFromContext ) open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/jaeger/doc.go000066400000000000000000000004671470323427300271520ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package jaeger implements the Jaeger propagator specification as defined at // https://www.jaegertracing.io/docs/1.18/client-libraries/#propagation-format package jaeger // import "go.opentelemetry.io/contrib/propagators/jaeger" open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/jaeger/go.mod000066400000000000000000000007731470323427300271640ustar00rootroot00000000000000module go.opentelemetry.io/contrib/propagators/jaeger go 1.22 require ( github.com/google/go-cmp v0.6.0 github.com/stretchr/testify v1.9.0 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/trace v1.31.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/jaeger/go.sum000066400000000000000000000037151470323427300272100ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/jaeger/jaeger_data_test.go000066400000000000000000000140141470323427300316630ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package jaeger_test import ( "fmt" "go.opentelemetry.io/otel/trace" ) const ( traceID15Str = "3ce929d0e0e4736" traceID16Str = "a3ce929d0e0e4736" traceID32Str = "a1ce929d0e0e4736a3ce929d0e0e4736" spanIDStr = "00f067aa0ba902b7" jaegerHeader = "uber-trace-id" ) var ( traceID15 = trace.TraceID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xce, 0x92, 0x9d, 0x0e, 0x0e, 0x47, 0x36} traceID16 = trace.TraceID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa3, 0xce, 0x92, 0x9d, 0x0e, 0x0e, 0x47, 0x36} traceID32 = trace.TraceID{0xa1, 0xce, 0x92, 0x9d, 0x0e, 0x0e, 0x47, 0x36, 0xa3, 0xce, 0x92, 0x9d, 0x0e, 0x0e, 0x47, 0x36} spanID = trace.SpanID{0x00, 0xf0, 0x67, 0xaa, 0x0b, 0xa9, 0x02, 0xb7} ) type extractTest struct { name string headers map[string]string expected trace.SpanContextConfig debug bool } var extractHeaders = []extractTest{ { "empty", map[string]string{}, trace.SpanContextConfig{}, false, }, { "sampling state not sample", map[string]string{ jaegerHeader: fmt.Sprintf("%s:%s:0:0", traceID32Str, spanIDStr), }, trace.SpanContextConfig{ TraceID: traceID32, SpanID: spanID, }, false, }, { "sampling state sampled", map[string]string{ jaegerHeader: fmt.Sprintf("%s:%s:0:1", traceID32Str, spanIDStr), }, trace.SpanContextConfig{ TraceID: traceID32, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, false, }, { "sampling state debug", map[string]string{ jaegerHeader: fmt.Sprintf("%s:%s:0:3", traceID32Str, spanIDStr), }, trace.SpanContextConfig{ TraceID: traceID32, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, true, }, { "sampling state debug but sampled bit didn't set, result in not sampled decision", map[string]string{ jaegerHeader: fmt.Sprintf("%s:%s:0:2", traceID32Str, spanIDStr), }, trace.SpanContextConfig{ TraceID: traceID32, SpanID: spanID, }, false, }, { "flag can be various length", map[string]string{ jaegerHeader: fmt.Sprintf("%s:%s:0:00001", traceID32Str, spanIDStr), }, trace.SpanContextConfig{ TraceID: traceID32, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, false, }, { "flag can be hex numbers", map[string]string{ jaegerHeader: fmt.Sprintf("%s:%s:0:ff", traceID32Str, spanIDStr), }, trace.SpanContextConfig{ TraceID: traceID32, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, true, }, { "left padding 60 bit trace ID", map[string]string{ jaegerHeader: fmt.Sprintf("%s:%s:0:1", traceID15Str, spanIDStr), }, trace.SpanContextConfig{ TraceID: traceID15, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, false, }, { "left padding 64 bit trace ID", map[string]string{ jaegerHeader: fmt.Sprintf("%s:%s:0:1", traceID16Str, spanIDStr), }, trace.SpanContextConfig{ TraceID: traceID16, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, false, }, { "128 bit trace ID", map[string]string{ jaegerHeader: fmt.Sprintf("%s:%s:0:1", traceID32Str, spanIDStr), }, trace.SpanContextConfig{ TraceID: traceID32, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, false, }, { "ignore parent span id", map[string]string{ jaegerHeader: fmt.Sprintf("%s:%s:whatever:1", traceID32Str, spanIDStr), }, trace.SpanContextConfig{ TraceID: traceID32, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, false, }, } var invalidExtractHeaders = []extractTest{ { name: "trace ID length > 32", headers: map[string]string{ jaegerHeader: fmt.Sprintf("%s:%s:0:1", traceID32Str+"0000", spanIDStr), }, }, { name: "span ID length is not 16 or 32", headers: map[string]string{ jaegerHeader: fmt.Sprintf("%s:%s:0:1", traceID32Str, spanIDStr+"0000"), }, }, { name: "invalid trace ID", headers: map[string]string{ jaegerHeader: fmt.Sprintf("%s:%s:0:1", "zcd00v0000000000a3ce929d0e0e4736", spanIDStr), }, }, { name: "invalid span ID", headers: map[string]string{ jaegerHeader: fmt.Sprintf("%s:%s:0:1", traceID32Str, "00f0wiredba902b7"), }, }, { name: "invalid flags", headers: map[string]string{ jaegerHeader: fmt.Sprintf("%s:%s:0:wired", traceID32Str, spanIDStr), }, }, { name: "invalid separator", headers: map[string]string{ jaegerHeader: fmt.Sprintf("%s-%s-0-1", traceID32Str, spanIDStr), }, }, { name: "missing jaeger header", headers: map[string]string{ jaegerHeader + "not": fmt.Sprintf("%s:%s:0:1", traceID32Str, spanIDStr), }, }, { name: "empty header value", headers: map[string]string{ jaegerHeader: "", }, }, } type injectTest struct { name string scc trace.SpanContextConfig wantHeaders map[string]string debug bool } var injectHeaders = []injectTest{ { name: "sampled", scc: trace.SpanContextConfig{ TraceID: traceID32, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, wantHeaders: map[string]string{ jaegerHeader: fmt.Sprintf("%s:%s:0:1", traceID32Str, spanIDStr), }, }, { name: "debug", scc: trace.SpanContextConfig{ TraceID: traceID32, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, wantHeaders: map[string]string{ jaegerHeader: fmt.Sprintf("%s:%s:0:3", traceID32Str, spanIDStr), }, debug: true, }, { name: "not sampled", scc: trace.SpanContextConfig{ TraceID: traceID32, SpanID: spanID, }, wantHeaders: map[string]string{ jaegerHeader: fmt.Sprintf("%s:%s:0:0", traceID32Str, spanIDStr), }, }, } var invalidInjectHeaders = []injectTest{ { name: "empty", scc: trace.SpanContextConfig{}, }, { name: "missing traceID", scc: trace.SpanContextConfig{ SpanID: spanID, TraceFlags: trace.FlagsSampled, }, }, { name: "missing spanID", scc: trace.SpanContextConfig{ TraceID: traceID32, TraceFlags: trace.FlagsSampled, }, }, { name: "missing both traceID and spanID", scc: trace.SpanContextConfig{ TraceFlags: trace.FlagsSampled, }, }, } open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/jaeger/jaeger_example_test.go000066400000000000000000000004521470323427300324060ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package jaeger_test import ( "go.opentelemetry.io/contrib/propagators/jaeger" "go.opentelemetry.io/otel" ) func ExampleJaeger() { p := jaeger.Jaeger{} // register jaeger propagator otel.SetTextMapPropagator(p) } open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/jaeger/jaeger_integration_test.go000066400000000000000000000044401470323427300332770ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package jaeger_test import ( "context" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/google/go-cmp/cmp" "go.opentelemetry.io/contrib/propagators/jaeger" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" ) func TestExtractJaeger(t *testing.T) { testGroup := []struct { name string testcases []extractTest }{ { name: "valid test case", testcases: extractHeaders, }, { name: "invalid test case", testcases: invalidExtractHeaders, }, } for _, tg := range testGroup { propagator := jaeger.Jaeger{} for _, tc := range tg.testcases { t.Run(tc.name, func(t *testing.T) { header := make(http.Header, len(tc.headers)) for k, v := range tc.headers { header.Set(k, v) } ctx := context.Background() ctx = propagator.Extract(ctx, propagation.HeaderCarrier(header)) resSc := trace.SpanContextFromContext(ctx) comparer := cmp.Comparer(func(a, b trace.SpanContext) bool { // Do not compare remote field, it is unset on empty // SpanContext. newA := a.WithRemote(b.IsRemote()) return newA.Equal(b) }) if diff := cmp.Diff(resSc, trace.NewSpanContext(tc.expected), comparer); diff != "" { t.Errorf("%s: %s: -got +want %s", tg.name, tc.name, diff) } assert.Equal(t, tc.debug, jaeger.DebugFromContext(ctx)) }) } } } func TestInjectJaeger(t *testing.T) { testGroup := []struct { name string testcases []injectTest }{ { name: "valid test case", testcases: injectHeaders, }, { name: "invalid test case", testcases: invalidInjectHeaders, }, } for _, tg := range testGroup { for _, tc := range tg.testcases { propagator := jaeger.Jaeger{} t.Run(tc.name, func(t *testing.T) { header := http.Header{} ctx := trace.ContextWithSpanContext( jaeger.WithDebug(context.Background(), tc.debug), trace.NewSpanContext(tc.scc), ) propagator.Inject(ctx, propagation.HeaderCarrier(header)) for h, v := range tc.wantHeaders { result, want := header.Get(h), v if diff := cmp.Diff(result, want); diff != "" { t.Errorf("%s: %s, header=%s: -got +want %s", tg.name, tc.name, h, diff) } } }) } } } open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/jaeger/jaeger_propagator.go000066400000000000000000000110031470323427300320640ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package jaeger // import "go.opentelemetry.io/contrib/propagators/jaeger" import ( "context" "errors" "fmt" "strconv" "strings" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" ) const ( jaegerHeader = "uber-trace-id" separator = ":" traceID128bitsWidth = 128 / 4 spanIDWidth = 64 / 4 idPaddingChar = "0" flagsDebug = 0x02 flagsSampled = 0x01 flagsNotSampled = 0x00 deprecatedParentSpanID = "0" ) var ( empty = trace.SpanContext{} errMalformedTraceContextVal = errors.New("header value of uber-trace-id should contain four different part separated by : ") errInvalidTraceIDLength = errors.New("invalid trace id length, must be either 16 or 32") errMalformedTraceID = errors.New("cannot decode trace id from header, should be a string of hex, lowercase trace id can't be all zero") errInvalidSpanIDLength = errors.New("invalid span id length, must be 16") errMalformedSpanID = errors.New("cannot decode span id from header, should be a string of hex, lowercase span id can't be all zero") errMalformedFlag = errors.New("cannot decode flag") ) // Jaeger propagator serializes SpanContext to/from Jaeger Headers // // Jaeger format: // // uber-trace-id: {trace-id}:{span-id}:{parent-span-id}:{flags}. type Jaeger struct{} var _ propagation.TextMapPropagator = &Jaeger{} // Inject injects a context to the carrier following jaeger format. // The parent span ID is set to an dummy parent span id as the most implementations do. func (jaeger Jaeger) Inject(ctx context.Context, carrier propagation.TextMapCarrier) { sc := trace.SpanFromContext(ctx).SpanContext() headers := []string{} if !sc.TraceID().IsValid() || !sc.SpanID().IsValid() { return } headers = append(headers, sc.TraceID().String(), sc.SpanID().String(), deprecatedParentSpanID) if debugFromContext(ctx) { headers = append(headers, fmt.Sprintf("%x", flagsDebug|flagsSampled)) } else if sc.IsSampled() { headers = append(headers, fmt.Sprintf("%x", flagsSampled)) } else { headers = append(headers, fmt.Sprintf("%x", flagsNotSampled)) } carrier.Set(jaegerHeader, strings.Join(headers, separator)) } // Extract extracts a context from the carrier if it contains Jaeger headers. func (jaeger Jaeger) Extract(ctx context.Context, carrier propagation.TextMapCarrier) context.Context { // extract tracing information if h := carrier.Get(jaegerHeader); h != "" { ctx, sc, err := extract(ctx, h) if err == nil && sc.IsValid() { return trace.ContextWithRemoteSpanContext(ctx, sc) } } return ctx } func extract(ctx context.Context, headerVal string) (context.Context, trace.SpanContext, error) { var ( scc = trace.SpanContextConfig{} err error ) parts := strings.Split(headerVal, separator) if len(parts) != 4 { return ctx, empty, errMalformedTraceContextVal } // extract trace ID if parts[0] != "" { id := parts[0] if len(id) > traceID128bitsWidth { return ctx, empty, errInvalidTraceIDLength } // padding when length is less than 32 if len(id) < traceID128bitsWidth { padCharCount := traceID128bitsWidth - len(id) id = strings.Repeat(idPaddingChar, padCharCount) + id } scc.TraceID, err = trace.TraceIDFromHex(id) if err != nil { return ctx, empty, errMalformedTraceID } } // extract span ID if parts[1] != "" { id := parts[1] if len(id) > spanIDWidth { return ctx, empty, errInvalidSpanIDLength } // padding when length is less than 16 if len(id) < spanIDWidth { padCharCount := spanIDWidth - len(id) id = strings.Repeat(idPaddingChar, padCharCount) + id } scc.SpanID, err = trace.SpanIDFromHex(id) if err != nil { return ctx, empty, errMalformedSpanID } } // skip third part as it is deprecated // extract flag if parts[3] != "" { flagStr := parts[3] flag, err := strconv.ParseInt(flagStr, 16, 64) if err != nil { return ctx, empty, errMalformedFlag } if flag&flagsSampled == flagsSampled { // if sample bit is set, we check if debug bit is also set if flag&flagsDebug == flagsDebug { scc.TraceFlags |= trace.FlagsSampled ctx = withDebug(ctx, true) } else { scc.TraceFlags |= trace.FlagsSampled } } // ignore other bit, including firehose since we don't have corresponding flag in trace context. } return ctx, trace.NewSpanContext(scc), nil } // Fields returns the Jaeger header key whose value is set with Inject. func (jaeger Jaeger) Fields() []string { return []string{jaegerHeader} } open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/jaeger/jaeger_propagator_test.go000066400000000000000000000102171470323427300331310ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package jaeger import ( "context" "fmt" "strings" "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/trace" ) var ( traceID = trace.TraceID{0, 0, 0, 0, 0, 0, 0, 0, 0xb, 0, 0, 0, 0, 0, 0x1, 0xc8} traceID128Str = "00000000000000000b000000000001c8" zeroTraceIDStr = "00000000000000000000000000000000" traceID64Str = "0b000000000001c8" traceID60Str = "b000000000001c8" spanID = trace.SpanID{0, 0, 0, 0, 0, 0, 0, 0x7b} zeroSpanIDStr = "0000000000000000" spanID64Str = "000000000000007b" spanID60Str = "00000000000007b" ) func TestJaeger_Extract(t *testing.T) { testData := []struct { traceID string spanID string parentSpanID string flags string expected trace.SpanContextConfig err error debug bool }{ { traceID128Str, spanID64Str, deprecatedParentSpanID, "1", trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, nil, false, }, { traceID64Str, spanID64Str, deprecatedParentSpanID, "1", trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, nil, false, }, { traceID60Str, spanID60Str, deprecatedParentSpanID, "1", trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, nil, false, }, { traceID128Str, spanID64Str, deprecatedParentSpanID, "3", trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, nil, true, }, { // if we didn't set sampled bit when debug bit is 1, then assuming it's not sampled traceID128Str, spanID64Str, deprecatedParentSpanID, "2", trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: 0x00, }, nil, false, }, { // ignore firehose bit since we don't really have this feature in otel span context traceID128Str, spanID64Str, deprecatedParentSpanID, "8", trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: 0x00, }, nil, false, }, { traceID128Str, spanID64Str, deprecatedParentSpanID, "9", trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, nil, false, }, { traceID128Str, spanID64Str, "wired stuff", "1", trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, nil, false, }, { fmt.Sprintf("%32s", "This_is_a_string_len_64"), spanID64Str, deprecatedParentSpanID, "1", trace.SpanContextConfig{}, errMalformedTraceID, false, }, { "0000000000000007b00000000000001c8", spanID64Str, deprecatedParentSpanID, "1", trace.SpanContextConfig{}, errInvalidTraceIDLength, false, }, { traceID128Str, fmt.Sprintf("%16s", "wiredspanid"), deprecatedParentSpanID, "1", trace.SpanContextConfig{}, errMalformedSpanID, false, }, { traceID128Str, "00000000000000010", deprecatedParentSpanID, "1", trace.SpanContextConfig{}, errInvalidSpanIDLength, false, }, { // reject invalid traceID(0) and spanID(0) zeroTraceIDStr, zeroSpanIDStr, deprecatedParentSpanID, "1", trace.SpanContextConfig{}, errMalformedTraceID, false, }, { // reject invalid traceID(0) and spanID(0) traceID128Str, zeroSpanIDStr, deprecatedParentSpanID, "1", trace.SpanContextConfig{}, errMalformedSpanID, false, }, } for _, test := range testData { headerVal := strings.Join([]string{test.traceID, test.spanID, test.parentSpanID, test.flags}, separator) ctx, sc, err := extract(context.Background(), headerVal) info := []interface{}{ "trace ID: %q, span ID: %q, parent span ID: %q, sampled: %q, flags: %q", test.traceID, test.spanID, test.parentSpanID, test.flags, } if !assert.Equal(t, test.err, err, info...) { continue } assert.Equal(t, trace.NewSpanContext(test.expected), sc, info...) assert.Equal(t, test.debug, debugFromContext(ctx)) } } open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/jaeger/version.go000066400000000000000000000007701470323427300300670ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package jaeger // import "go.opentelemetry.io/contrib/propagators/jaeger" // Version is the current release version of the Jaeger propagator. func Version() string { return "1.31.0" // This string is updated by the pre_release.sh script during release } // SemVersion is the semantic version to be supplied to tracer/meter creation. // // Deprecated: Use [Version] instead. func SemVersion() string { return Version() } open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/opencensus/000077500000000000000000000000001470323427300267745ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/opencensus/README.md000066400000000000000000000021721470323427300302550ustar00rootroot00000000000000# OpenCensus Binary Propagation Format ## The Problem The [ocgrpc](https://github.com/census-instrumentation/opencensus-go/tree/master/plugin/ocgrpc) GRPC plugin for OpenCensus is hard-coded to use a [Binary propagation format](https://github.com/census-instrumentation/opencensus-go/blob/380f4078db9f3ee20e26a08105ceecccddf872b8/trace/propagation/propagation.go). A GRPC client and server that use OpenCensus cannot easily migrate to OpenTelemetry because there will be a period of time during which one will use OpenCensus and the other will use OpenTelemetry. If both client and server export spans to the same trace backend, the server spans will not be a child of the client spans, because they are using different propagation formats. To be able to easily migrate from OpenCensus to OpenTelemetry, it is necessary to use the OpenCensus binary propagation format with OpenTelemetry. ## Usage To add the binary propagation format with otelgrpc, use the WithPropagators option to the otelgrpc Interceptors: ```golang import "go.opentelemetry.io/contrib/propagators/opencensus" opt := otelgrpc.WithPropagators(opencensus.Binary{}) ``` open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/opencensus/binary.go000066400000000000000000000042541470323427300306140ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package opencensus // import "go.opentelemetry.io/contrib/propagators/opencensus" import ( "context" ocpropagation "go.opencensus.io/trace/propagation" "go.opentelemetry.io/otel/bridge/opencensus" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" ) type key uint const binaryKey key = 0 // binaryHeader is the same as traceContextKey is in opencensus: // https://github.com/census-instrumentation/opencensus-go/blob/3fb168f674736c026e623310bfccb0691e6dec8a/plugin/ocgrpc/trace_common.go#L30 const binaryHeader = "grpc-trace-bin" // Binary is an OpenTelemetry implementation of the OpenCensus grpc binary format. // Binary propagation was temporarily removed from opentelemetry. See // https://github.com/open-telemetry/opentelemetry-specification/issues/437 type Binary struct{} var _ propagation.TextMapPropagator = Binary{} // Inject injects context into the TextMapCarrier. func (b Binary) Inject(ctx context.Context, carrier propagation.TextMapCarrier) { binaryContext := ctx.Value(binaryKey) if state, ok := binaryContext.(string); binaryContext != nil && ok { carrier.Set(binaryHeader, state) } sc := trace.SpanContextFromContext(ctx) if !sc.IsValid() { return } h := ocpropagation.Binary(opencensus.OTelSpanContextToOC(sc)) carrier.Set(binaryHeader, string(h)) } // Extract extracts the SpanContext from the TextMapCarrier. func (b Binary) Extract(ctx context.Context, carrier propagation.TextMapCarrier) context.Context { state := carrier.Get(binaryHeader) if state != "" { ctx = context.WithValue(ctx, binaryKey, state) } sc := b.extract(carrier) if !sc.IsValid() { return ctx } return trace.ContextWithRemoteSpanContext(ctx, sc) } func (b Binary) extract(carrier propagation.TextMapCarrier) trace.SpanContext { h := carrier.Get(binaryHeader) if h == "" { return trace.SpanContext{} } ocContext, ok := ocpropagation.FromBinary([]byte(h)) if !ok { return trace.SpanContext{} } return opencensus.OCSpanContextToOTel(ocContext) } // Fields returns the fields that this propagator modifies. func (b Binary) Fields() []string { return []string{binaryHeader} } open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/opencensus/binary_test.go000066400000000000000000000072161470323427300316540ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package opencensus import ( "context" "fmt" "net/http" "testing" "github.com/google/go-cmp/cmp" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" ) var ( traceID = trace.TraceID([16]byte{14, 54, 12}) spanID = trace.SpanID([8]byte{0, 0, 0, 0, 0, 0, 0, 1}) childSpanID = trace.SpanID([8]byte{0, 0, 0, 0, 0, 0, 0, 2}) headerFmt = "\x00\x00\x0e6\f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00%s\x02%s" ) func TestFields(t *testing.T) { b := Binary{} fields := b.Fields() if len(fields) != 1 { t.Fatalf("Got %d fields, expected 1", len(fields)) } if fields[0] != "grpc-trace-bin" { t.Errorf("Got fields[0] == %s, expected grpc-trace-bin", fields[0]) } } func TestInject(t *testing.T) { prop := Binary{} for _, tt := range []struct { desc string scc trace.SpanContextConfig wantHeader string }{ { desc: "empty", scc: trace.SpanContextConfig{}, wantHeader: "", }, { desc: "valid spancontext, sampled", scc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, wantHeader: fmt.Sprintf(headerFmt, "\x01", "\x01"), }, { desc: "valid spancontext, not sampled", scc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, }, wantHeader: fmt.Sprintf(headerFmt, "\x01", "\x00"), }, { desc: "valid spancontext, with unsupported bit set in traceflags", scc: trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: 0xff, }, wantHeader: fmt.Sprintf(headerFmt, "\x01", "\x01"), }, { desc: "invalid spancontext", scc: trace.SpanContextConfig{}, wantHeader: "", }, } { t.Run(tt.desc, func(t *testing.T) { header := http.Header{} ctx := context.Background() if sc := trace.NewSpanContext(tt.scc); sc.IsValid() { ctx = trace.ContextWithRemoteSpanContext(ctx, sc) } prop.Inject(ctx, propagation.HeaderCarrier(header)) gotHeader := header.Get("grpc-trace-bin") if gotHeader != tt.wantHeader { t.Errorf("Got header = %q, want %q", gotHeader, tt.wantHeader) } }) } } func TestExtract(t *testing.T) { prop := Binary{} for _, tt := range []struct { desc string header string wantScc trace.SpanContextConfig }{ { desc: "empty", header: "", wantScc: trace.SpanContextConfig{}, }, { desc: "header not binary", header: "5435j345io34t5904w3jt894j3t854w89tp95jgt9", wantScc: trace.SpanContextConfig{}, }, { desc: "valid binary header", header: fmt.Sprintf(headerFmt, "\x02", "\x00"), wantScc: trace.SpanContextConfig{ TraceID: traceID, SpanID: childSpanID, }, }, { desc: "valid binary and sampled", header: fmt.Sprintf(headerFmt, "\x02", "\x01"), wantScc: trace.SpanContextConfig{ TraceID: traceID, SpanID: childSpanID, TraceFlags: trace.FlagsSampled, }, }, } { t.Run(tt.desc, func(t *testing.T) { header := http.Header{ http.CanonicalHeaderKey("grpc-trace-bin"): []string{tt.header}, } ctx := context.Background() ctx = prop.Extract(ctx, propagation.HeaderCarrier(header)) gotSc := trace.SpanContextFromContext(ctx) comparer := cmp.Comparer(func(a, b trace.SpanContext) bool { // Do not compare remote field, it is unset on empty // SpanContext. newA := a.WithRemote(b.IsRemote()) return newA.Equal(b) }) if diff := cmp.Diff(gotSc, trace.NewSpanContext(tt.wantScc), comparer); diff != "" { t.Errorf("%s: -got +want %s", tt.desc, diff) } }) } } open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/opencensus/examples/000077500000000000000000000000001470323427300306125ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/opencensus/examples/README.md000066400000000000000000000024741470323427300321000ustar00rootroot00000000000000# OpenCensus binary propagation example The server uses OpenTelemetry with the OpenCensus binary propagation format. The client uses OpenCensus, which is hard-coded to use the OpenCensus binary propagation format. Since the client and server use the same propagation format, the ParentSpanID from the server spans should match the SpanID from the client spans, and both should share the same TraceID. ### Usage First, start the opentelemetry server: ```bash go run opentelemetry_server/server.go ``` In another shell, start the OpenCensus client: ```bash go run opencensus_client/client.go ``` ### Example Client Output ``` Configuring OpenCensus, and registering the Print exporter. TraceID: 9d59b1bdbde34cdaac6cfb5b8f3c4685 SpanID: 07733a2559ef492d ... Greeting: Hello world ``` Note that there is no ParentSpanID listed in the client. ### Example Server Output ``` Registering opentelemetry stdout exporter. Starting the GRPC server, and using the OpenCensus binary propagation format. [ { "SpanContext": { "TraceID": "9d59b1bdbde34cdaac6cfb5b8f3c4685", "SpanID": "94738571415fdb63", "TraceFlags": 1 }, "ParentSpanID": "07733a2559ef492d", ... } ] ``` The TraceID matches the TraceID from the OpenCensus client span, and the ParentSpanID matches the SpanID of the OpenCensus client span. open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/opencensus/examples/go.mod000066400000000000000000000025471470323427300317300ustar00rootroot00000000000000module go.opentelemetry.io/contrib/propagators/opencensus/examples go 1.22 require ( go.opencensus.io v0.24.0 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.56.0 go.opentelemetry.io/contrib/propagators/opencensus v0.56.0 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0 go.opentelemetry.io/otel/sdk v1.31.0 google.golang.org/grpc v1.67.1 ) require ( github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/uuid v1.6.0 // indirect go.opentelemetry.io/otel/bridge/opencensus v1.31.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.31.0 // indirect go.opentelemetry.io/otel/trace v1.31.0 // indirect golang.org/x/net v0.30.0 // indirect golang.org/x/sys v0.26.0 // indirect golang.org/x/text v0.19.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 // indirect google.golang.org/protobuf v1.35.1 // indirect ) replace ( go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc => ../../../instrumentation/google.golang.org/grpc/otelgrpc go.opentelemetry.io/contrib/propagators/opencensus => ../ ) open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/opencensus/examples/go.sum000066400000000000000000000315421470323427300317520ustar00rootroot00000000000000cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/bridge/opencensus v1.31.0 h1:YrCZ8NpdMTunNIzRnNoG3KjSLu0PNmRtgtQVJuCxkAQ= go.opentelemetry.io/otel/bridge/opencensus v1.31.0/go.mod h1:2yEkg7WRb15imAr0jfS4XDNd8LNe/hRES+kFezyO6LI= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0 h1:UGZ1QwZWY67Z6BmckTU+9Rxn04m2bD3gD6Mk0OIOCPk= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0/go.mod h1:fcwWuDuaObkkChiDlhEpSq9+X1C0omv+s5mBtToAQ64= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc= go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 h1:QCqS/PdaHTSWGvupk2F/ehwHtGc0/GYkT+3GAcR1CCc= google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/opencensus/examples/opencensus_client/000077500000000000000000000000001470323427300343325ustar00rootroot00000000000000client.go000066400000000000000000000024671470323427300360710ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/opencensus/examples/opencensus_client// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package main import ( "context" "log" "os" "time" "go.opencensus.io/examples/exporter" pb "go.opencensus.io/examples/grpc/proto" "go.opencensus.io/plugin/ocgrpc" "go.opencensus.io/trace" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" ) const ( address = "localhost:50051" defaultName = "world" ) func main() { log.Println("Configuring OpenCensus, and registering the Print exporter.") trace.ApplyConfig(trace.Config{DefaultSampler: trace.AlwaysSample()}) trace.RegisterExporter(&exporter.PrintExporter{}) // Set up a connection to the server with the OpenCensus // stats handler to enable tracing. conn, err := grpc.NewClient(address, grpc.WithStatsHandler(&ocgrpc.ClientHandler{}), grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { log.Fatalf("Cannot connect: %v", err) } defer conn.Close() c := pb.NewGreeterClient(conn) // Contact the server and print out its response. name := defaultName if len(os.Args) > 1 { name = os.Args[1] } for { r, err := c.SayHello(context.Background(), &pb.HelloRequest{Name: name}) if err != nil { log.Printf("Could not greet: %v", err) } else { log.Printf("Greeting: %s", r.Message) } time.Sleep(2 * time.Second) } } opentelemetry_server/000077500000000000000000000000001470323427300350155ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/opencensus/examplesserver.go000066400000000000000000000041051470323427300366520ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/opencensus/examples/opentelemetry_server// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package main // import "go.opentelemetry.io/otel/bridge/opencensus/examples/grpc/server" import ( "context" "log" "math/rand" "net" "time" pb "go.opencensus.io/examples/grpc/proto" "go.opencensus.io/trace" "google.golang.org/grpc" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" "go.opentelemetry.io/contrib/propagators/opencensus" "go.opentelemetry.io/otel" stdout "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" sdktrace "go.opentelemetry.io/otel/sdk/trace" ) const address = "localhost:50051" // server is used to implement helloworld.GreeterServer. type server struct{} // SayHello implements helloworld.GreeterServer. func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { _, span := trace.StartSpan(ctx, "sleep") time.Sleep(time.Duration(rand.Float64() * float64(time.Second))) //nolint:gosec // Ignoring G404: Use of weak random number generator (math/rand instead of crypto/rand) span.End() return &pb.HelloReply{Message: "Hello " + in.Name}, nil } func main() { lis, err := net.Listen("tcp", address) if err != nil { log.Fatalf("Failed to listen: %v", err) } log.Println("Registering OpenTelemetry stdout exporter.") otExporter, err := stdout.New(stdout.WithPrettyPrint()) if err != nil { log.Fatal(err) } tp := sdktrace.NewTracerProvider( sdktrace.WithBatcher(otExporter), sdktrace.WithSampler(sdktrace.AlwaysSample()), ) defer func() { if err := tp.Shutdown(context.Background()); err != nil { log.Printf("Error shutting down tracer provider: %v", err) } }() otel.SetTracerProvider(tp) // Set up a new server with the OpenCensus // handler to enable tracing. log.Println("Starting the GRPC server, and using the OpenCensus binary propagation format.") s := grpc.NewServer( grpc.StatsHandler(otelgrpc.NewServerHandler(otelgrpc.WithPropagators(opencensus.Binary{})))) pb.RegisterGreeterServer(s, &server{}) if err := s.Serve(lis); err != nil { log.Fatalf("Failed to serve: %v", err) } } open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/opencensus/go.mod000066400000000000000000000012541470323427300301040ustar00rootroot00000000000000module go.opentelemetry.io/contrib/propagators/opencensus go 1.22 require ( github.com/google/go-cmp v0.6.0 go.opencensus.io v0.24.0 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/bridge/opencensus v1.31.0 go.opentelemetry.io/otel/trace v1.31.0 ) require ( github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/google/uuid v1.6.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect go.opentelemetry.io/otel/sdk v1.31.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.31.0 // indirect golang.org/x/sys v0.26.0 // indirect ) open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/opencensus/go.sum000066400000000000000000000271151470323427300301350ustar00rootroot00000000000000cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/bridge/opencensus v1.31.0 h1:YrCZ8NpdMTunNIzRnNoG3KjSLu0PNmRtgtQVJuCxkAQ= go.opentelemetry.io/otel/bridge/opencensus v1.31.0/go.mod h1:2yEkg7WRb15imAr0jfS4XDNd8LNe/hRES+kFezyO6LI= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc= go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/opencensus/version.go000066400000000000000000000010041470323427300310030ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package opencensus // import "go.opentelemetry.io/contrib/propagators/opencensus" // Version is the current release version of the OpenCensus propagator. func Version() string { return "0.56.0" // This string is updated by the pre_release.sh script during release } // SemVersion is the semantic version to be supplied to tracer/meter creation. // // Deprecated: Use [Version] instead. func SemVersion() string { return Version() } open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/ot/000077500000000000000000000000001470323427300252345ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/ot/doc.go000066400000000000000000000004201470323427300263240ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package ot implements the ot-tracer-* propagator used by the default Tracer // implementation from the OpenTracing project. package ot // import "go.opentelemetry.io/contrib/propagators/ot" open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/ot/go.mod000066400000000000000000000010251470323427300263400ustar00rootroot00000000000000module go.opentelemetry.io/contrib/propagators/ot go 1.22 require ( github.com/google/go-cmp v0.6.0 github.com/stretchr/testify v1.9.0 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/trace v1.31.0 go.uber.org/multierr v1.11.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/ot/go.sum000066400000000000000000000041561470323427300263750ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/ot/ot_data_test.go000066400000000000000000000143011470323427300302340ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package ot_test import ( "strings" "go.opentelemetry.io/otel/trace" ) const ( traceID16Str = "a3ce929d0e0e4736" traceID32Str = "a1ce929d0e0e4736a3ce929d0e0e4736" spanIDStr = "00f067aa0ba902b7" traceIDHeader = "ot-tracer-traceid" spanIDHeader = "ot-tracer-spanid" sampledHeader = "ot-tracer-sampled" baggageKey = "test" baggageValue = "value123" baggageHeader = "ot-baggage-test" baggageKey2 = "test2" baggageValue2 = "value456" baggageHeader2 = "ot-baggage-test2" ) var ( traceID16 = trace.TraceID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa3, 0xce, 0x92, 0x9d, 0x0e, 0x0e, 0x47, 0x36} traceID32 = trace.TraceID{0xa1, 0xce, 0x92, 0x9d, 0x0e, 0x0e, 0x47, 0x36, 0xa3, 0xce, 0x92, 0x9d, 0x0e, 0x0e, 0x47, 0x36} spanID = trace.SpanID{0x00, 0xf0, 0x67, 0xaa, 0x0b, 0xa9, 0x02, 0xb7} emptyBaggage = map[string]string{} baggageSet = map[string]string{ baggageKey: baggageValue, } baggageSet2 = map[string]string{ baggageKey: baggageValue, baggageKey2: baggageValue2, } ) type extractTest struct { name string headers map[string]string expected trace.SpanContextConfig baggage map[string]string } var extractHeaders = []extractTest{ { "empty", map[string]string{}, trace.SpanContextConfig{}, emptyBaggage, }, { "sampling state not sample", map[string]string{ traceIDHeader: traceID32Str, spanIDHeader: spanIDStr, sampledHeader: "0", }, trace.SpanContextConfig{ TraceID: traceID32, SpanID: spanID, }, emptyBaggage, }, { "sampling state sampled", map[string]string{ traceIDHeader: traceID32Str, spanIDHeader: spanIDStr, sampledHeader: "1", baggageHeader: baggageValue, }, trace.SpanContextConfig{ TraceID: traceID32, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, baggageSet, }, { "baggage multiple values", map[string]string{ traceIDHeader: traceID32Str, spanIDHeader: spanIDStr, sampledHeader: "0", baggageHeader: baggageValue, baggageHeader2: baggageValue2, }, trace.SpanContextConfig{ TraceID: traceID32, SpanID: spanID, }, baggageSet2, }, { "left padding 64 bit trace ID", map[string]string{ traceIDHeader: traceID16Str, spanIDHeader: spanIDStr, sampledHeader: "1", }, trace.SpanContextConfig{ TraceID: traceID16, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, emptyBaggage, }, { "128 bit trace ID", map[string]string{ traceIDHeader: traceID32Str, spanIDHeader: spanIDStr, sampledHeader: "1", }, trace.SpanContextConfig{ TraceID: traceID32, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, emptyBaggage, }, } var invalidExtractHeaders = []extractTest{ { name: "trace ID length > 32", headers: map[string]string{ traceIDHeader: traceID32Str + "0000", spanIDHeader: spanIDStr, sampledHeader: "1", }, }, { name: "trace ID length is not 32 or 16", headers: map[string]string{ traceIDHeader: "1234567890abcd01234", spanIDHeader: spanIDStr, sampledHeader: "1", }, }, { name: "span ID length is not 16 or 32", headers: map[string]string{ traceIDHeader: traceID32Str, spanIDHeader: spanIDStr + "0000", sampledHeader: "1", }, }, { name: "invalid trace ID", headers: map[string]string{ traceIDHeader: "zcd00v0000000000a3ce929d0e0e4736", spanIDHeader: spanIDStr, sampledHeader: "1", }, }, { name: "invalid span ID", headers: map[string]string{ traceIDHeader: traceID32Str, spanIDHeader: "00f0wiredba902b7", sampledHeader: "1", }, }, { name: "invalid sampled", headers: map[string]string{ traceIDHeader: traceID32Str, spanIDHeader: spanIDStr, sampledHeader: "wired", }, }, { name: "invalid baggage key", headers: map[string]string{ traceIDHeader: traceID32Str, spanIDHeader: spanIDStr, sampledHeader: "1", "ot-baggage-d–76": "test", }, expected: trace.SpanContextConfig{ TraceID: traceID32, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, }, { name: "invalid baggage value", headers: map[string]string{ traceIDHeader: traceID32Str, spanIDHeader: spanIDStr, sampledHeader: "1", baggageHeader: "øtel", }, expected: trace.SpanContextConfig{ TraceID: traceID32, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, }, { name: "invalid baggage result (too large)", headers: map[string]string{ traceIDHeader: traceID32Str, spanIDHeader: spanIDStr, sampledHeader: "1", baggageHeader: strings.Repeat("s", 8188), }, expected: trace.SpanContextConfig{ TraceID: traceID32, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, }, { name: "missing headers", headers: map[string]string{}, }, { name: "empty header value", headers: map[string]string{ traceIDHeader: "", }, }, } type injectTest struct { name string sc trace.SpanContextConfig wantHeaders map[string]string baggage map[string]string } var injectHeaders = []injectTest{ { name: "sampled", sc: trace.SpanContextConfig{ TraceID: traceID32, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, wantHeaders: map[string]string{ traceIDHeader: traceID16Str, spanIDHeader: spanIDStr, sampledHeader: "true", }, }, { name: "not sampled", sc: trace.SpanContextConfig{ TraceID: traceID32, SpanID: spanID, }, baggage: map[string]string{ baggageKey: baggageValue, baggageKey2: baggageValue2, }, wantHeaders: map[string]string{ traceIDHeader: traceID16Str, spanIDHeader: spanIDStr, sampledHeader: "false", baggageHeader: baggageValue, baggageHeader2: baggageValue2, }, }, } var invalidInjectHeaders = []injectTest{ { name: "empty", sc: trace.SpanContextConfig{}, }, { name: "missing traceID", sc: trace.SpanContextConfig{ SpanID: spanID, TraceFlags: trace.FlagsSampled, }, }, { name: "missing spanID", sc: trace.SpanContextConfig{ TraceID: traceID32, TraceFlags: trace.FlagsSampled, }, }, { name: "missing both traceID and spanID", sc: trace.SpanContextConfig{ TraceFlags: trace.FlagsSampled, }, }, } open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/ot/ot_example_test.go000066400000000000000000000004501470323427300307560ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package ot_test import ( "go.opentelemetry.io/contrib/propagators/ot" "go.opentelemetry.io/otel" ) func ExampleOT() { otPropagator := ot.OT{} // register ot propagator otel.SetTextMapPropagator(otPropagator) } open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/ot/ot_integration_test.go000066400000000000000000000056601470323427300316560ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package ot_test import ( "context" "net/http" "testing" "github.com/google/go-cmp/cmp" "go.opentelemetry.io/contrib/propagators/ot" "go.opentelemetry.io/otel/baggage" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" ) func TestExtractOT(t *testing.T) { testGroup := []struct { name string testcases []extractTest }{ { name: "valid test case", testcases: extractHeaders, }, { name: "invalid test case", testcases: invalidExtractHeaders, }, } for _, tg := range testGroup { propagator := ot.OT{} for _, tc := range tg.testcases { t.Run(tc.name, func(t *testing.T) { h := make(http.Header, len(tc.headers)) for k, v := range tc.headers { h.Set(k, v) } ctx := context.Background() ctx = propagator.Extract(ctx, propagation.HeaderCarrier(h)) resSc := trace.SpanContextFromContext(ctx) comparer := cmp.Comparer(func(a, b trace.SpanContext) bool { // Do not compare remote field, it is unset on empty // SpanContext. newA := a.WithRemote(b.IsRemote()) return newA.Equal(b) }) if diff := cmp.Diff(resSc, trace.NewSpanContext(tc.expected), comparer); diff != "" { t.Errorf("%s: %s: -got +want %s", tg.name, tc.name, diff) } members := baggage.FromContext(ctx).Members() actualBaggage := map[string]string{} for _, m := range members { actualBaggage[m.Key()] = m.Value() } if diff := cmp.Diff(tc.baggage, actualBaggage); tc.baggage != nil && diff != "" { t.Errorf("%s: %s: -got +want %s", tg.name, tc.name, diff) } }) } } } func TestInjectOT(t *testing.T) { testGroup := []struct { name string testcases []injectTest }{ { name: "valid test case", testcases: injectHeaders, }, { name: "invalid test case", testcases: invalidInjectHeaders, }, } for _, tg := range testGroup { for _, tc := range tg.testcases { propagator := ot.OT{} t.Run(tc.name, func(t *testing.T) { members := []baggage.Member{} for k, v := range tc.baggage { m, err := baggage.NewMember(k, v) if err != nil { t.Errorf("%s: %s, unexpected error creating baggage member: %s", tg.name, tc.name, err.Error()) } members = append(members, m) } bag, err := baggage.New(members...) if err != nil { t.Errorf("%s: %s, unexpected error creating baggage: %s", tg.name, tc.name, err.Error()) } ctx := baggage.ContextWithBaggage(context.Background(), bag) ctx = trace.ContextWithSpanContext(ctx, trace.NewSpanContext(tc.sc)) header := http.Header{} propagator.Inject(ctx, propagation.HeaderCarrier(header)) for h, v := range tc.wantHeaders { result, want := header.Get(h), v if diff := cmp.Diff(result, want); diff != "" { t.Errorf("%s: %s, header=%s: -got +want %s", tg.name, tc.name, h, diff) } } }) } } } open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/ot/ot_propagator.go000066400000000000000000000106441470323427300304500ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package ot // import "go.opentelemetry.io/contrib/propagators/ot" import ( "context" "errors" "fmt" "strings" "go.uber.org/multierr" "go.opentelemetry.io/otel/baggage" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" ) const ( // Default OT Header names. traceIDHeader = "ot-tracer-traceid" spanIDHeader = "ot-tracer-spanid" sampledHeader = "ot-tracer-sampled" baggageHeaderPrefix = "ot-baggage-" otTraceIDPadding = "0000000000000000" traceID64BitsWidth = 64 / 4 // 16 hex character Trace ID. ) var ( empty = trace.SpanContext{} errInvalidSampledHeader = errors.New("invalid OT Sampled header found") errInvalidTraceIDHeader = errors.New("invalid OT traceID header found") errInvalidSpanIDHeader = errors.New("invalid OT spanID header found") errInvalidScope = errors.New("require either both traceID and spanID or none") ) // OT propagator serializes SpanContext to/from ot-trace-* headers. type OT struct{} var _ propagation.TextMapPropagator = OT{} // Inject injects a context into the carrier as OT headers. // NOTE: In order to interop with systems that use the OT header format, trace ids MUST be 64-bits. func (o OT) Inject(ctx context.Context, carrier propagation.TextMapCarrier) { sc := trace.SpanFromContext(ctx).SpanContext() if !sc.TraceID().IsValid() || !sc.SpanID().IsValid() { // don't bother injecting anything if either trace or span IDs are not valid return } carrier.Set(traceIDHeader, sc.TraceID().String()[len(sc.TraceID().String())-traceID64BitsWidth:]) carrier.Set(spanIDHeader, sc.SpanID().String()) if sc.IsSampled() { carrier.Set(sampledHeader, "true") } else { carrier.Set(sampledHeader, "false") } for _, m := range baggage.FromContext(ctx).Members() { carrier.Set(fmt.Sprintf("%s%s", baggageHeaderPrefix, m.Key()), m.Value()) } } // Extract extracts a context from the carrier if it contains OT headers. func (o OT) Extract(ctx context.Context, carrier propagation.TextMapCarrier) context.Context { var ( sc trace.SpanContext err error ) var ( traceID = carrier.Get(traceIDHeader) spanID = carrier.Get(spanIDHeader) sampled = carrier.Get(sampledHeader) ) sc, err = extract(traceID, spanID, sampled) if err != nil || !sc.IsValid() { return ctx } bags, err := extractBags(carrier) if err != nil { return trace.ContextWithRemoteSpanContext(ctx, sc) } ctx = baggage.ContextWithBaggage(ctx, bags) return trace.ContextWithRemoteSpanContext(ctx, sc) } // Fields returns the OT header keys whose values are set with Inject. func (o OT) Fields() []string { return []string{traceIDHeader, spanIDHeader, sampledHeader} } // extractBags extracts OpenTracing baggage information from carrier. func extractBags(carrier propagation.TextMapCarrier) (baggage.Baggage, error) { var err error var members []baggage.Member for _, key := range carrier.Keys() { lowerKey := strings.ToLower(key) if !strings.HasPrefix(lowerKey, baggageHeaderPrefix) { continue } strippedKey := strings.TrimPrefix(lowerKey, baggageHeaderPrefix) member, e := baggage.NewMember(strippedKey, carrier.Get(key)) if e != nil { err = multierr.Append(err, e) continue } members = append(members, member) } bags, e := baggage.New(members...) if err != nil { return bags, multierr.Append(err, e) } return bags, err } // extract reconstructs a SpanContext from header values based on OT // headers. func extract(traceID, spanID, sampled string) (trace.SpanContext, error) { var ( err error requiredCount int scc = trace.SpanContextConfig{} ) switch strings.ToLower(sampled) { case "0", "false": // Zero value for TraceFlags sample bit is unset. case "1", "true": scc.TraceFlags = trace.FlagsSampled case "": // Zero value for TraceFlags sample bit is unset. default: return empty, errInvalidSampledHeader } if traceID != "" { requiredCount++ id := traceID if len(traceID) == 16 { // Pad 64-bit trace IDs. id = otTraceIDPadding + traceID } if scc.TraceID, err = trace.TraceIDFromHex(id); err != nil { return empty, errInvalidTraceIDHeader } } if spanID != "" { requiredCount++ if scc.SpanID, err = trace.SpanIDFromHex(spanID); err != nil { return empty, errInvalidSpanIDHeader } } if requiredCount != 0 && requiredCount != 2 { return empty, errInvalidScope } return trace.NewSpanContext(scc), nil } open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/ot/ot_propagator_test.go000066400000000000000000000055441470323427300315120ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package ot import ( "fmt" "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/trace" ) var ( traceID = trace.TraceID{0, 0, 0, 0, 0, 0, 0, 0, 0x7b, 0, 0, 0, 0, 0, 0x1, 0xc8} traceID128Str = "00000000000000007b000000000001c8" zeroTraceIDStr = "00000000000000000000000000000000" traceID64Str = "7b000000000001c8" spanID = trace.SpanID{0, 0, 0, 0, 0, 0, 0, 0x7b} zeroSpanIDStr = "0000000000000000" spanIDStr = "000000000000007b" ) func TestOT_Extract(t *testing.T) { testData := []struct { traceID string spanID string sampled string expected trace.SpanContextConfig err error }{ { traceID128Str, spanIDStr, "1", trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, nil, }, { traceID64Str, spanIDStr, "1", trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, nil, }, { traceID128Str, spanIDStr, "", trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: 0x00, }, nil, }, { // if we didn't set sampled bit when debug bit is 1, then assuming it's not sampled traceID128Str, spanIDStr, "0", trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: 0x00, }, nil, }, { traceID128Str, spanIDStr, "1", trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, nil, }, { fmt.Sprintf("%32s", "This_is_a_string_len_64"), spanIDStr, "1", trace.SpanContextConfig{}, errInvalidTraceIDHeader, }, { "000000000007b00000000000001c8", spanIDStr, "1", trace.SpanContextConfig{}, errInvalidTraceIDHeader, }, { traceID128Str, fmt.Sprintf("%16s", "wiredspanid"), "1", trace.SpanContextConfig{}, errInvalidSpanIDHeader, }, { traceID128Str, "0000000000010", "1", trace.SpanContextConfig{}, errInvalidSpanIDHeader, }, { // reject invalid traceID(0) and spanID(0) zeroTraceIDStr, zeroSpanIDStr, "1", trace.SpanContextConfig{}, errInvalidTraceIDHeader, }, { // reject invalid spanID(0) traceID128Str, zeroSpanIDStr, "1", trace.SpanContextConfig{}, errInvalidSpanIDHeader, }, { // reject invalid spanID(0) traceID128Str, spanIDStr, "invalid", trace.SpanContextConfig{}, errInvalidSampledHeader, }, } for _, test := range testData { sc, err := extract(test.traceID, test.spanID, test.sampled) info := []interface{}{ "trace ID: %q, span ID: %q, sampled: %q", test.traceID, test.spanID, test.sampled, } if !assert.Equal(t, test.err, err, info...) { continue } assert.Equal(t, trace.NewSpanContext(test.expected), sc, info...) } } open-telemetry-opentelemetry-go-contrib-e5abccb/propagators/ot/version.go000066400000000000000000000007541470323427300272560ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package ot // import "go.opentelemetry.io/contrib/propagators/ot" // Version is the current release version of the ot propagator. func Version() string { return "1.31.0" // This string is updated by the pre_release.sh script during release } // SemVersion is the semantic version to be supplied to tracer/meter creation. // // Deprecated: Use [Version] instead. func SemVersion() string { return Version() } open-telemetry-opentelemetry-go-contrib-e5abccb/renovate.json000066400000000000000000000016511470323427300247720ustar00rootroot00000000000000{ "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ "config:recommended" ], "ignorePaths": [], "ignoreDeps": [ "go.opentelemetry.io/contrib/instrgen" ], "labels": ["Skip Changelog", "dependencies"], "separateMajorMinor": true, "postUpdateOptions" : [ "gomodTidy" ], "packageRules": [ { "matchManagers": ["gomod"], "matchDepTypes": ["indirect"], "enabled": true }, { "matchFileNames": ["tools/**"], "matchManagers": ["gomod"], "matchDepTypes": ["indirect"], "enabled": false }, { "matchPackageNames": ["google.golang.org/genproto/googleapis/**"], "groupName": "googleapis" }, { "matchPackageNames": ["golang.org/x/**"], "groupName": "golang.org/x" }, { "matchPackageNames": ["go.opentelemetry.io/otel/**"], "allowedVersions": "/^v[0-9]+\\.[0-9]+\\.[0-9]+/" } ] } open-telemetry-opentelemetry-go-contrib-e5abccb/requirements.txt000066400000000000000000000000201470323427300255250ustar00rootroot00000000000000codespell==2.3.0open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/000077500000000000000000000000001470323427300240775ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/jaegerremote/000077500000000000000000000000001470323427300265505ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/jaegerremote/Makefile000066400000000000000000000005211470323427300302060ustar00rootroot00000000000000PROTOC=docker run --rm -v${PWD}:${PWD} -w${PWD} otel/build-protobuf:latest --proto_path=${PWD} PROTO_INCLUDES=-I/usr/include/github.com/gogo/protobuf .PHONY: proto-gen proto-gen: mkdir -p internal/proto-gen $(PROTOC) $(PROTO_INCLUDES) \ --gogo_out=$(PWD)/internal/proto-gen \ ${PWD}/jaeger-idl/proto/api_v2/sampling.proto open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/jaegerremote/README.md000066400000000000000000000064121470323427300300320ustar00rootroot00000000000000# Jaeger Remote Sampler This package implements [Jaeger remote sampler](https://www.jaegertracing.io/docs/latest/sampling/#remote-sampling). Remote sampler allows defining sampling configuration for services at the backend, at the granularity of service + endpoint. When using the Jaeger backend, the sampling configuration can come from two sources: 1. A static configuration file, with the ability to hot-reload it on changes. 2. [Adaptive sampling](https://www.jaegertracing.io/docs/latest/sampling/#adaptive-sampling) where Jaeger backend automatically calculates desired sampling probabilities based on the target volume of trace data per service. ## Usage Configuration in the code: ```go jaegerRemoteSampler := jaegerremote.New( "your-service-name", jaegerremote.WithSamplingServerURL("http://{sampling_service_host_name}:5778/sampling"), jaegerremote.WithSamplingRefreshInterval(10*time.Second), jaegerremote.WithInitialSampler(trace.TraceIDRatioBased(0.5)), ) tp := trace.NewTracerProvider( trace.WithSampler(jaegerRemoteSampler), ... ) otel.SetTracerProvider(tp) ``` Sampling server: * Historically, the Jaeger Agent provided the sampling server at `http://{agent_host}:5778/sampling`. * When not running the Jaeger Agent, the sampling server is also provided by the Jaeger Collector, but at a slightly different endpoint: `http://collector_host:14268/api/sampling`. * The OpenTelemetry Collector can provide the sampling endpoint `http://{otel_collector_host}:5778/sampling` by [configuring an extension](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/extension/jaegerremotesampling/README.md). Notes: * At this time, the Jaeger Remote Sampler can only be configured in the code, configuration via `OTEL_TRACES_SAMPLER=jaeger_sampler` environment variable is not supported. * Service name must be passed to the constructor. It will be used by the sampler to poll the backend for the sampling strategy for this service. * Both Jaeger Agent and OpenTelemetry Collector implement the Jaeger sampling service endpoint. ## Example [example/](./example) shows how to host remote sampling strategies using the OpenTelemetry Collector. The Collector uses the Jaeger receiver to host the strategy file. Note you do not need to run Jaeger to make use of the Jaeger remote sampling protocol. However, you do need Jaeger backend if you want to utilize its adaptive sampling engine that auto-calculates remote sampling strategies. Run the OpenTelemetry Collector using docker-compose: ```shell $ docker-compose up -d ``` You can fetch the strategy file using curl: ```shell $ curl 'localhost:5778/sampling?service=foo' $ curl 'localhost:5778/sampling?service=myService' ``` Run the Go program. This program will start with an initial sampling percentage of 50% and tries to fetch the sampling strategies from the OpenTelemetry Collector. It will print the entire Jaeger remote sampler structure every 10 seconds, this allows you to observe the internal sampler. ```shell $ go run . ``` ## Update generated Jaeger code Code is generated using the .proto files from [jaeger-idl](https://github.com/jaegertracing/jaeger-idl). In case [sampling.proto](./jaeger-idl/proto/api_v2/sampling.proto) is modified these have to be regenerated. ```shell $ make proto-gen ``` open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/jaegerremote/constants.go000066400000000000000000000022031470323427300311100ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Copyright (c) 2021 The Jaeger Authors. // Copyright (c) 2017 Uber Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package jaegerremote // import "go.opentelemetry.io/contrib/samplers/jaegerremote" import ( "fmt" ) const ( // defaultSamplingServerPort is the default port to fetch sampling config from, via http. defaultSamplingServerPort = 5778 ) // defaultSamplingServerURL is the default url to fetch sampling config from, via http. var defaultSamplingServerURL = fmt.Sprintf("http://127.0.0.1:%d/sampling", defaultSamplingServerPort) open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/jaegerremote/doc.go000066400000000000000000000015501470323427300276450ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Copyright (c) 2021 The Jaeger Authors. // Copyright (c) 2017 Uber Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package jaegerremote implements the Jaeger Remote protocol. package jaegerremote // import "go.opentelemetry.io/contrib/samplers/jaegerremote" open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/jaegerremote/example/000077500000000000000000000000001470323427300302035ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/jaegerremote/example/docker-compose.yaml000066400000000000000000000005071470323427300340030ustar00rootroot00000000000000version: "3" services: otel-collector: image: otel/opentelemetry-collector:latest command: [ "--config=/etc/otel-collector.yaml" ] volumes: - ./otel-collector.yaml:/etc/otel-collector.yaml - ./strategies.json:/etc/strategies.json ports: - "5778:5778" # default jaeger remote sampling port open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/jaegerremote/example/go.mod000066400000000000000000000015241470323427300313130ustar00rootroot00000000000000module go.opentelemetry.io/contrib/samplers/jaegerremote/example go 1.22 require ( github.com/davecgh/go-spew v1.1.1 go.opentelemetry.io/contrib/samplers/jaegerremote v0.25.0 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0 go.opentelemetry.io/otel/sdk v1.31.0 ) require ( github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/uuid v1.6.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect go.opentelemetry.io/otel/trace v1.31.0 // indirect golang.org/x/sys v0.26.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 // indirect google.golang.org/protobuf v1.35.1 // indirect ) replace go.opentelemetry.io/contrib/samplers/jaegerremote => ../ open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/jaegerremote/example/go.sum000066400000000000000000000136531470323427300313460ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0 h1:UGZ1QwZWY67Z6BmckTU+9Rxn04m2bD3gD6Mk0OIOCPk= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0/go.mod h1:fcwWuDuaObkkChiDlhEpSq9+X1C0omv+s5mBtToAQ64= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 h1:T6rh4haD3GVYsgEfWExoCZA2o2FmbNyKpTuAxbEFPTg= google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:wp2WsuBYj6j8wUdo3ToZsdxxixbvQNAHqVJrTgi5E5M= google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/jaegerremote/example/main.go000066400000000000000000000021521470323427300314560ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package main import ( "fmt" "time" "github.com/davecgh/go-spew/spew" "go.opentelemetry.io/contrib/samplers/jaegerremote" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/sdk/trace" ) func main() { jaegerRemoteSampler := jaegerremote.New( "foo", jaegerremote.WithSamplingServerURL("http://localhost:5778"), jaegerremote.WithSamplingRefreshInterval(10*time.Second), // decrease polling interval to get quicker feedback jaegerremote.WithInitialSampler(trace.TraceIDRatioBased(0.5)), ) exporter, _ := stdouttrace.New() tp := trace.NewTracerProvider( trace.WithSampler(jaegerRemoteSampler), trace.WithSyncer(exporter), // for production usage, use trace.WithBatcher(exporter) ) otel.SetTracerProvider(tp) ticker := time.Tick(time.Second) for { <-ticker fmt.Printf("\n* Jaeger Remote Sampler %v\n\n", time.Now()) spewCfg := spew.ConfigState{ Indent: " ", DisablePointerAddresses: true, } spewCfg.Dump(jaegerRemoteSampler) } } open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/jaegerremote/example/otel-collector.yaml000066400000000000000000000004501470323427300340150ustar00rootroot00000000000000receivers: jaeger: protocols: grpc: remote_sampling: host_endpoint: "0.0.0.0:5778" # default port insecure: true strategy_file: "/etc/strategies.json" exporters: debug: service: pipelines: traces: receivers: [ jaeger ] exporters: [ debug ] open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/jaegerremote/example/strategies.json000066400000000000000000000010311470323427300332430ustar00rootroot00000000000000{ "service_strategies": [ { "service": "foo", "type": "probabilistic", "param": 0.8, "operation_strategies": [ { "operation": "op1", "type": "probabilistic", "param": 0.2 }, { "operation": "op2", "type": "probabilistic", "param": 0.4 } ] }, { "service": "bar", "type": "ratelimiting", "param": 5 } ], "default_strategy": { "type": "probabilistic", "param": 0.2 } } open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/jaegerremote/go.mod000066400000000000000000000013631470323427300276610ustar00rootroot00000000000000module go.opentelemetry.io/contrib/samplers/jaegerremote go 1.22 require ( github.com/go-logr/logr v1.4.2 github.com/gogo/protobuf v1.3.2 github.com/stretchr/testify v1.9.0 go.opentelemetry.io/otel/sdk v1.31.0 go.opentelemetry.io/otel/trace v1.31.0 google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/otel v1.31.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect golang.org/x/sys v0.26.0 // indirect google.golang.org/protobuf v1.35.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/jaegerremote/go.sum000066400000000000000000000136311470323427300277070ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 h1:T6rh4haD3GVYsgEfWExoCZA2o2FmbNyKpTuAxbEFPTg= google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:wp2WsuBYj6j8wUdo3ToZsdxxixbvQNAHqVJrTgi5E5M= google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/jaegerremote/internal/000077500000000000000000000000001470323427300303645ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/jaegerremote/internal/proto-gen/000077500000000000000000000000001470323427300322765ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/jaegerremote/internal/proto-gen/jaeger-idl/000077500000000000000000000000001470323427300343015ustar00rootroot00000000000000proto/000077500000000000000000000000001470323427300353655ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/jaegerremote/internal/proto-gen/jaeger-idlapi_v2/000077500000000000000000000000001470323427300365455ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/jaegerremote/internal/proto-gen/jaeger-idl/protosampling.pb.go000066400000000000000000001260331470323427300413130ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/jaegerremote/internal/proto-gen/jaeger-idl/proto/api_v2// Code generated by protoc-gen-gogo. DO NOT EDIT. // source: jaeger-idl/proto/api_v2/sampling.proto package api_v2 import ( encoding_binary "encoding/binary" fmt "fmt" _ "github.com/gogo/protobuf/gogoproto" proto "github.com/gogo/protobuf/proto" _ "google.golang.org/genproto/googleapis/api/annotations" io "io" math "math" math_bits "math/bits" ) // Reference imports to suppress errors if they are not otherwise used. var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. // A compilation error at this line likely means your copy of the // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package type SamplingStrategyType int32 const ( SamplingStrategyType_PROBABILISTIC SamplingStrategyType = 0 SamplingStrategyType_RATE_LIMITING SamplingStrategyType = 1 ) var SamplingStrategyType_name = map[int32]string{ 0: "PROBABILISTIC", 1: "RATE_LIMITING", } var SamplingStrategyType_value = map[string]int32{ "PROBABILISTIC": 0, "RATE_LIMITING": 1, } func (x SamplingStrategyType) String() string { return proto.EnumName(SamplingStrategyType_name, int32(x)) } func (SamplingStrategyType) EnumDescriptor() ([]byte, []int) { return fileDescriptor_ae32d90db01957f7, []int{0} } type ProbabilisticSamplingStrategy struct { SamplingRate float64 `protobuf:"fixed64,1,opt,name=samplingRate,proto3" json:"samplingRate,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } func (m *ProbabilisticSamplingStrategy) Reset() { *m = ProbabilisticSamplingStrategy{} } func (m *ProbabilisticSamplingStrategy) String() string { return proto.CompactTextString(m) } func (*ProbabilisticSamplingStrategy) ProtoMessage() {} func (*ProbabilisticSamplingStrategy) Descriptor() ([]byte, []int) { return fileDescriptor_ae32d90db01957f7, []int{0} } func (m *ProbabilisticSamplingStrategy) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *ProbabilisticSamplingStrategy) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { return xxx_messageInfo_ProbabilisticSamplingStrategy.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) if err != nil { return nil, err } return b[:n], nil } } func (m *ProbabilisticSamplingStrategy) XXX_Merge(src proto.Message) { xxx_messageInfo_ProbabilisticSamplingStrategy.Merge(m, src) } func (m *ProbabilisticSamplingStrategy) XXX_Size() int { return m.Size() } func (m *ProbabilisticSamplingStrategy) XXX_DiscardUnknown() { xxx_messageInfo_ProbabilisticSamplingStrategy.DiscardUnknown(m) } var xxx_messageInfo_ProbabilisticSamplingStrategy proto.InternalMessageInfo func (m *ProbabilisticSamplingStrategy) GetSamplingRate() float64 { if m != nil { return m.SamplingRate } return 0 } type RateLimitingSamplingStrategy struct { MaxTracesPerSecond int32 `protobuf:"varint,1,opt,name=maxTracesPerSecond,proto3" json:"maxTracesPerSecond,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } func (m *RateLimitingSamplingStrategy) Reset() { *m = RateLimitingSamplingStrategy{} } func (m *RateLimitingSamplingStrategy) String() string { return proto.CompactTextString(m) } func (*RateLimitingSamplingStrategy) ProtoMessage() {} func (*RateLimitingSamplingStrategy) Descriptor() ([]byte, []int) { return fileDescriptor_ae32d90db01957f7, []int{1} } func (m *RateLimitingSamplingStrategy) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *RateLimitingSamplingStrategy) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { return xxx_messageInfo_RateLimitingSamplingStrategy.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) if err != nil { return nil, err } return b[:n], nil } } func (m *RateLimitingSamplingStrategy) XXX_Merge(src proto.Message) { xxx_messageInfo_RateLimitingSamplingStrategy.Merge(m, src) } func (m *RateLimitingSamplingStrategy) XXX_Size() int { return m.Size() } func (m *RateLimitingSamplingStrategy) XXX_DiscardUnknown() { xxx_messageInfo_RateLimitingSamplingStrategy.DiscardUnknown(m) } var xxx_messageInfo_RateLimitingSamplingStrategy proto.InternalMessageInfo func (m *RateLimitingSamplingStrategy) GetMaxTracesPerSecond() int32 { if m != nil { return m.MaxTracesPerSecond } return 0 } type OperationSamplingStrategy struct { Operation string `protobuf:"bytes,1,opt,name=operation,proto3" json:"operation,omitempty"` ProbabilisticSampling *ProbabilisticSamplingStrategy `protobuf:"bytes,2,opt,name=probabilisticSampling,proto3" json:"probabilisticSampling,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } func (m *OperationSamplingStrategy) Reset() { *m = OperationSamplingStrategy{} } func (m *OperationSamplingStrategy) String() string { return proto.CompactTextString(m) } func (*OperationSamplingStrategy) ProtoMessage() {} func (*OperationSamplingStrategy) Descriptor() ([]byte, []int) { return fileDescriptor_ae32d90db01957f7, []int{2} } func (m *OperationSamplingStrategy) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *OperationSamplingStrategy) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { return xxx_messageInfo_OperationSamplingStrategy.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) if err != nil { return nil, err } return b[:n], nil } } func (m *OperationSamplingStrategy) XXX_Merge(src proto.Message) { xxx_messageInfo_OperationSamplingStrategy.Merge(m, src) } func (m *OperationSamplingStrategy) XXX_Size() int { return m.Size() } func (m *OperationSamplingStrategy) XXX_DiscardUnknown() { xxx_messageInfo_OperationSamplingStrategy.DiscardUnknown(m) } var xxx_messageInfo_OperationSamplingStrategy proto.InternalMessageInfo func (m *OperationSamplingStrategy) GetOperation() string { if m != nil { return m.Operation } return "" } func (m *OperationSamplingStrategy) GetProbabilisticSampling() *ProbabilisticSamplingStrategy { if m != nil { return m.ProbabilisticSampling } return nil } type PerOperationSamplingStrategies struct { DefaultSamplingProbability float64 `protobuf:"fixed64,1,opt,name=defaultSamplingProbability,proto3" json:"defaultSamplingProbability,omitempty"` DefaultLowerBoundTracesPerSecond float64 `protobuf:"fixed64,2,opt,name=defaultLowerBoundTracesPerSecond,proto3" json:"defaultLowerBoundTracesPerSecond,omitempty"` PerOperationStrategies []*OperationSamplingStrategy `protobuf:"bytes,3,rep,name=perOperationStrategies,proto3" json:"perOperationStrategies,omitempty"` DefaultUpperBoundTracesPerSecond float64 `protobuf:"fixed64,4,opt,name=defaultUpperBoundTracesPerSecond,proto3" json:"defaultUpperBoundTracesPerSecond,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } func (m *PerOperationSamplingStrategies) Reset() { *m = PerOperationSamplingStrategies{} } func (m *PerOperationSamplingStrategies) String() string { return proto.CompactTextString(m) } func (*PerOperationSamplingStrategies) ProtoMessage() {} func (*PerOperationSamplingStrategies) Descriptor() ([]byte, []int) { return fileDescriptor_ae32d90db01957f7, []int{3} } func (m *PerOperationSamplingStrategies) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *PerOperationSamplingStrategies) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { return xxx_messageInfo_PerOperationSamplingStrategies.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) if err != nil { return nil, err } return b[:n], nil } } func (m *PerOperationSamplingStrategies) XXX_Merge(src proto.Message) { xxx_messageInfo_PerOperationSamplingStrategies.Merge(m, src) } func (m *PerOperationSamplingStrategies) XXX_Size() int { return m.Size() } func (m *PerOperationSamplingStrategies) XXX_DiscardUnknown() { xxx_messageInfo_PerOperationSamplingStrategies.DiscardUnknown(m) } var xxx_messageInfo_PerOperationSamplingStrategies proto.InternalMessageInfo func (m *PerOperationSamplingStrategies) GetDefaultSamplingProbability() float64 { if m != nil { return m.DefaultSamplingProbability } return 0 } func (m *PerOperationSamplingStrategies) GetDefaultLowerBoundTracesPerSecond() float64 { if m != nil { return m.DefaultLowerBoundTracesPerSecond } return 0 } func (m *PerOperationSamplingStrategies) GetPerOperationStrategies() []*OperationSamplingStrategy { if m != nil { return m.PerOperationStrategies } return nil } func (m *PerOperationSamplingStrategies) GetDefaultUpperBoundTracesPerSecond() float64 { if m != nil { return m.DefaultUpperBoundTracesPerSecond } return 0 } type SamplingStrategyResponse struct { StrategyType SamplingStrategyType `protobuf:"varint,1,opt,name=strategyType,proto3,enum=jaeger.api_v2.SamplingStrategyType" json:"strategyType,omitempty"` ProbabilisticSampling *ProbabilisticSamplingStrategy `protobuf:"bytes,2,opt,name=probabilisticSampling,proto3" json:"probabilisticSampling,omitempty"` RateLimitingSampling *RateLimitingSamplingStrategy `protobuf:"bytes,3,opt,name=rateLimitingSampling,proto3" json:"rateLimitingSampling,omitempty"` OperationSampling *PerOperationSamplingStrategies `protobuf:"bytes,4,opt,name=operationSampling,proto3" json:"operationSampling,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } func (m *SamplingStrategyResponse) Reset() { *m = SamplingStrategyResponse{} } func (m *SamplingStrategyResponse) String() string { return proto.CompactTextString(m) } func (*SamplingStrategyResponse) ProtoMessage() {} func (*SamplingStrategyResponse) Descriptor() ([]byte, []int) { return fileDescriptor_ae32d90db01957f7, []int{4} } func (m *SamplingStrategyResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *SamplingStrategyResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { return xxx_messageInfo_SamplingStrategyResponse.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) if err != nil { return nil, err } return b[:n], nil } } func (m *SamplingStrategyResponse) XXX_Merge(src proto.Message) { xxx_messageInfo_SamplingStrategyResponse.Merge(m, src) } func (m *SamplingStrategyResponse) XXX_Size() int { return m.Size() } func (m *SamplingStrategyResponse) XXX_DiscardUnknown() { xxx_messageInfo_SamplingStrategyResponse.DiscardUnknown(m) } var xxx_messageInfo_SamplingStrategyResponse proto.InternalMessageInfo func (m *SamplingStrategyResponse) GetStrategyType() SamplingStrategyType { if m != nil { return m.StrategyType } return SamplingStrategyType_PROBABILISTIC } func (m *SamplingStrategyResponse) GetProbabilisticSampling() *ProbabilisticSamplingStrategy { if m != nil { return m.ProbabilisticSampling } return nil } func (m *SamplingStrategyResponse) GetRateLimitingSampling() *RateLimitingSamplingStrategy { if m != nil { return m.RateLimitingSampling } return nil } func (m *SamplingStrategyResponse) GetOperationSampling() *PerOperationSamplingStrategies { if m != nil { return m.OperationSampling } return nil } type SamplingStrategyParameters struct { ServiceName string `protobuf:"bytes,1,opt,name=serviceName,proto3" json:"serviceName,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } func (m *SamplingStrategyParameters) Reset() { *m = SamplingStrategyParameters{} } func (m *SamplingStrategyParameters) String() string { return proto.CompactTextString(m) } func (*SamplingStrategyParameters) ProtoMessage() {} func (*SamplingStrategyParameters) Descriptor() ([]byte, []int) { return fileDescriptor_ae32d90db01957f7, []int{5} } func (m *SamplingStrategyParameters) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *SamplingStrategyParameters) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { return xxx_messageInfo_SamplingStrategyParameters.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) if err != nil { return nil, err } return b[:n], nil } } func (m *SamplingStrategyParameters) XXX_Merge(src proto.Message) { xxx_messageInfo_SamplingStrategyParameters.Merge(m, src) } func (m *SamplingStrategyParameters) XXX_Size() int { return m.Size() } func (m *SamplingStrategyParameters) XXX_DiscardUnknown() { xxx_messageInfo_SamplingStrategyParameters.DiscardUnknown(m) } var xxx_messageInfo_SamplingStrategyParameters proto.InternalMessageInfo func (m *SamplingStrategyParameters) GetServiceName() string { if m != nil { return m.ServiceName } return "" } func init() { proto.RegisterEnum("jaeger.api_v2.SamplingStrategyType", SamplingStrategyType_name, SamplingStrategyType_value) proto.RegisterType((*ProbabilisticSamplingStrategy)(nil), "jaeger.api_v2.ProbabilisticSamplingStrategy") proto.RegisterType((*RateLimitingSamplingStrategy)(nil), "jaeger.api_v2.RateLimitingSamplingStrategy") proto.RegisterType((*OperationSamplingStrategy)(nil), "jaeger.api_v2.OperationSamplingStrategy") proto.RegisterType((*PerOperationSamplingStrategies)(nil), "jaeger.api_v2.PerOperationSamplingStrategies") proto.RegisterType((*SamplingStrategyResponse)(nil), "jaeger.api_v2.SamplingStrategyResponse") proto.RegisterType((*SamplingStrategyParameters)(nil), "jaeger.api_v2.SamplingStrategyParameters") } func init() { proto.RegisterFile("jaeger-idl/proto/api_v2/sampling.proto", fileDescriptor_ae32d90db01957f7) } var fileDescriptor_ae32d90db01957f7 = []byte{ // 577 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x54, 0xcf, 0x6f, 0x12, 0x41, 0x14, 0x76, 0x40, 0x9b, 0xf4, 0xd1, 0x6a, 0x3b, 0x56, 0x5d, 0x09, 0x25, 0x64, 0x9b, 0x28, 0x56, 0x81, 0x64, 0xbd, 0x19, 0xd3, 0xa4, 0x34, 0x86, 0xac, 0xa1, 0x94, 0x2c, 0x78, 0xd1, 0x03, 0x0e, 0xf0, 0xdc, 0x8c, 0x81, 0x9d, 0xcd, 0xec, 0xb4, 0xca, 0xd5, 0xc4, 0xab, 0x17, 0xcf, 0x5e, 0xfc, 0x6b, 0x3c, 0x9a, 0x78, 0xf3, 0x64, 0x88, 0x7f, 0x88, 0xd9, 0x5f, 0x2d, 0x2c, 0x0b, 0xdc, 0x3c, 0xed, 0xe6, 0xbd, 0x6f, 0xbe, 0xef, 0x7b, 0x6f, 0xde, 0x3c, 0x78, 0xf0, 0x9e, 0xa1, 0x8d, 0xb2, 0xc2, 0x87, 0xa3, 0x9a, 0x2b, 0x85, 0x12, 0x35, 0xe6, 0xf2, 0xde, 0x85, 0x51, 0xf3, 0xd8, 0xd8, 0x1d, 0x71, 0xc7, 0xae, 0x06, 0x51, 0xba, 0x1d, 0xe2, 0xaa, 0x61, 0x36, 0xbf, 0x67, 0x0b, 0x5b, 0x84, 0x78, 0xff, 0x2f, 0x04, 0xe5, 0x0b, 0xb6, 0x10, 0xf6, 0x08, 0x7d, 0x8a, 0x1a, 0x73, 0x1c, 0xa1, 0x98, 0xe2, 0xc2, 0xf1, 0xc2, 0xac, 0x7e, 0x02, 0xfb, 0x6d, 0x29, 0xfa, 0xac, 0xcf, 0x47, 0xdc, 0x53, 0x7c, 0xd0, 0x89, 0x14, 0x3a, 0x4a, 0x32, 0x85, 0xf6, 0x84, 0xea, 0xb0, 0x15, 0xab, 0x5a, 0x4c, 0xa1, 0x46, 0x4a, 0xa4, 0x4c, 0xac, 0xb9, 0x98, 0xde, 0x82, 0x82, 0xff, 0x6d, 0xf2, 0x31, 0x57, 0xfe, 0xd9, 0x24, 0x47, 0x15, 0xe8, 0x98, 0x7d, 0xec, 0x4a, 0x36, 0x40, 0xaf, 0x8d, 0xb2, 0x83, 0x03, 0xe1, 0x0c, 0x03, 0xa6, 0x1b, 0x56, 0x4a, 0x46, 0xff, 0x46, 0xe0, 0xfe, 0x99, 0x8b, 0x32, 0x70, 0xba, 0xc0, 0x56, 0x80, 0x4d, 0x11, 0x27, 0x03, 0x92, 0x4d, 0xeb, 0x2a, 0x40, 0xfb, 0x70, 0xc7, 0x4d, 0x2b, 0x48, 0xcb, 0x94, 0x48, 0x39, 0x67, 0x3c, 0xa9, 0xce, 0xf5, 0xac, 0xba, 0xb2, 0x78, 0x2b, 0x9d, 0x4a, 0xff, 0x9d, 0x81, 0x62, 0x1b, 0xe5, 0x32, 0x8b, 0x1c, 0x3d, 0x7a, 0x04, 0xf9, 0x21, 0xbe, 0x63, 0xe7, 0x23, 0x15, 0x27, 0x2f, 0x95, 0xd4, 0x24, 0x6a, 0xe2, 0x0a, 0x04, 0x7d, 0x09, 0xa5, 0x28, 0xdb, 0x14, 0x1f, 0x50, 0xd6, 0xc5, 0xb9, 0x33, 0x4c, 0x36, 0x30, 0x13, 0xb0, 0xac, 0xc5, 0xd1, 0xb7, 0x70, 0xd7, 0x9d, 0x75, 0x7b, 0xe9, 0x52, 0xcb, 0x96, 0xb2, 0xe5, 0x9c, 0x51, 0x4e, 0xf4, 0x64, 0x69, 0xeb, 0xad, 0x25, 0x3c, 0x33, 0x6e, 0x5f, 0xb9, 0xee, 0x12, 0xb7, 0xd7, 0xe7, 0xdc, 0x2e, 0xc5, 0xe9, 0x9f, 0xb3, 0xa0, 0x2d, 0x08, 0xa3, 0xe7, 0x0a, 0xc7, 0x43, 0xda, 0x80, 0x2d, 0x2f, 0x8a, 0x75, 0x27, 0x6e, 0x38, 0x8d, 0x37, 0x8d, 0x83, 0x44, 0x01, 0xc9, 0xe3, 0x3e, 0xd4, 0x9a, 0x3b, 0xf8, 0x3f, 0xc6, 0x84, 0xf6, 0x60, 0x4f, 0xa6, 0x3c, 0x0b, 0x2d, 0x1b, 0x48, 0x3c, 0x4e, 0x48, 0xac, 0x7a, 0x41, 0x56, 0x2a, 0x11, 0x7d, 0x03, 0xbb, 0x22, 0x79, 0x57, 0x41, 0x9f, 0x73, 0x46, 0x25, 0x59, 0xc0, 0xca, 0x71, 0xb5, 0x16, 0x79, 0xf4, 0x23, 0xc8, 0x27, 0x6d, 0xb4, 0x99, 0x64, 0x63, 0x54, 0x28, 0x3d, 0x5a, 0x82, 0x9c, 0x87, 0xf2, 0x82, 0x0f, 0xb0, 0xc5, 0xc6, 0x18, 0x3d, 0xc3, 0xd9, 0xd0, 0xe1, 0x73, 0xd8, 0x4b, 0xbb, 0x07, 0xba, 0x0b, 0xdb, 0x6d, 0xeb, 0xac, 0x7e, 0x5c, 0x37, 0x9b, 0x66, 0xa7, 0x6b, 0x9e, 0xec, 0x5c, 0xf3, 0x43, 0xd6, 0x71, 0xf7, 0x45, 0xaf, 0x69, 0x9e, 0x9a, 0x5d, 0xb3, 0xd5, 0xd8, 0x21, 0xc6, 0x77, 0x02, 0xb7, 0xe2, 0xe3, 0xa7, 0xcc, 0x61, 0x36, 0x4a, 0xfa, 0x85, 0xc0, 0xed, 0x06, 0xaa, 0x85, 0x85, 0xf0, 0x68, 0xcd, 0xf5, 0x5f, 0xd9, 0xce, 0x3f, 0x5c, 0x03, 0x8d, 0x07, 0x4d, 0x3f, 0xf8, 0xf4, 0xeb, 0xef, 0xd7, 0xcc, 0xbe, 0xae, 0x05, 0x7b, 0x73, 0x66, 0xf5, 0xc6, 0xc8, 0x67, 0xe4, 0xb0, 0x5e, 0xf9, 0x31, 0x2d, 0x92, 0x9f, 0xd3, 0x22, 0xf9, 0x33, 0x2d, 0x12, 0xb8, 0xc7, 0x45, 0xc4, 0xae, 0x24, 0x1b, 0xf8, 0x5b, 0x3a, 0x14, 0x79, 0xbd, 0x11, 0x7e, 0xfb, 0x1b, 0xc1, 0xca, 0x7d, 0xfa, 0x2f, 0x00, 0x00, 0xff, 0xff, 0x60, 0xd2, 0xcd, 0xa7, 0xdf, 0x05, 0x00, 0x00, } func (m *ProbabilisticSamplingStrategy) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) if err != nil { return nil, err } return dAtA[:n], nil } func (m *ProbabilisticSamplingStrategy) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *ProbabilisticSamplingStrategy) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l if m.XXX_unrecognized != nil { i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } if m.SamplingRate != 0 { i -= 8 encoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(math.Float64bits(float64(m.SamplingRate)))) i-- dAtA[i] = 0x9 } return len(dAtA) - i, nil } func (m *RateLimitingSamplingStrategy) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) if err != nil { return nil, err } return dAtA[:n], nil } func (m *RateLimitingSamplingStrategy) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *RateLimitingSamplingStrategy) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l if m.XXX_unrecognized != nil { i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } if m.MaxTracesPerSecond != 0 { i = encodeVarintSampling(dAtA, i, uint64(m.MaxTracesPerSecond)) i-- dAtA[i] = 0x8 } return len(dAtA) - i, nil } func (m *OperationSamplingStrategy) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) if err != nil { return nil, err } return dAtA[:n], nil } func (m *OperationSamplingStrategy) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *OperationSamplingStrategy) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l if m.XXX_unrecognized != nil { i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } if m.ProbabilisticSampling != nil { { size, err := m.ProbabilisticSampling.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintSampling(dAtA, i, uint64(size)) } i-- dAtA[i] = 0x12 } if len(m.Operation) > 0 { i -= len(m.Operation) copy(dAtA[i:], m.Operation) i = encodeVarintSampling(dAtA, i, uint64(len(m.Operation))) i-- dAtA[i] = 0xa } return len(dAtA) - i, nil } func (m *PerOperationSamplingStrategies) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) if err != nil { return nil, err } return dAtA[:n], nil } func (m *PerOperationSamplingStrategies) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *PerOperationSamplingStrategies) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l if m.XXX_unrecognized != nil { i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } if m.DefaultUpperBoundTracesPerSecond != 0 { i -= 8 encoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(math.Float64bits(float64(m.DefaultUpperBoundTracesPerSecond)))) i-- dAtA[i] = 0x21 } if len(m.PerOperationStrategies) > 0 { for iNdEx := len(m.PerOperationStrategies) - 1; iNdEx >= 0; iNdEx-- { { size, err := m.PerOperationStrategies[iNdEx].MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintSampling(dAtA, i, uint64(size)) } i-- dAtA[i] = 0x1a } } if m.DefaultLowerBoundTracesPerSecond != 0 { i -= 8 encoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(math.Float64bits(float64(m.DefaultLowerBoundTracesPerSecond)))) i-- dAtA[i] = 0x11 } if m.DefaultSamplingProbability != 0 { i -= 8 encoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(math.Float64bits(float64(m.DefaultSamplingProbability)))) i-- dAtA[i] = 0x9 } return len(dAtA) - i, nil } func (m *SamplingStrategyResponse) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) if err != nil { return nil, err } return dAtA[:n], nil } func (m *SamplingStrategyResponse) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *SamplingStrategyResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l if m.XXX_unrecognized != nil { i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } if m.OperationSampling != nil { { size, err := m.OperationSampling.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintSampling(dAtA, i, uint64(size)) } i-- dAtA[i] = 0x22 } if m.RateLimitingSampling != nil { { size, err := m.RateLimitingSampling.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintSampling(dAtA, i, uint64(size)) } i-- dAtA[i] = 0x1a } if m.ProbabilisticSampling != nil { { size, err := m.ProbabilisticSampling.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } i -= size i = encodeVarintSampling(dAtA, i, uint64(size)) } i-- dAtA[i] = 0x12 } if m.StrategyType != 0 { i = encodeVarintSampling(dAtA, i, uint64(m.StrategyType)) i-- dAtA[i] = 0x8 } return len(dAtA) - i, nil } func (m *SamplingStrategyParameters) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) if err != nil { return nil, err } return dAtA[:n], nil } func (m *SamplingStrategyParameters) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *SamplingStrategyParameters) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l if m.XXX_unrecognized != nil { i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } if len(m.ServiceName) > 0 { i -= len(m.ServiceName) copy(dAtA[i:], m.ServiceName) i = encodeVarintSampling(dAtA, i, uint64(len(m.ServiceName))) i-- dAtA[i] = 0xa } return len(dAtA) - i, nil } func encodeVarintSampling(dAtA []byte, offset int, v uint64) int { offset -= sovSampling(v) base := offset for v >= 1<<7 { dAtA[offset] = uint8(v&0x7f | 0x80) v >>= 7 offset++ } dAtA[offset] = uint8(v) return base } func (m *ProbabilisticSamplingStrategy) Size() (n int) { if m == nil { return 0 } var l int _ = l if m.SamplingRate != 0 { n += 9 } if m.XXX_unrecognized != nil { n += len(m.XXX_unrecognized) } return n } func (m *RateLimitingSamplingStrategy) Size() (n int) { if m == nil { return 0 } var l int _ = l if m.MaxTracesPerSecond != 0 { n += 1 + sovSampling(uint64(m.MaxTracesPerSecond)) } if m.XXX_unrecognized != nil { n += len(m.XXX_unrecognized) } return n } func (m *OperationSamplingStrategy) Size() (n int) { if m == nil { return 0 } var l int _ = l l = len(m.Operation) if l > 0 { n += 1 + l + sovSampling(uint64(l)) } if m.ProbabilisticSampling != nil { l = m.ProbabilisticSampling.Size() n += 1 + l + sovSampling(uint64(l)) } if m.XXX_unrecognized != nil { n += len(m.XXX_unrecognized) } return n } func (m *PerOperationSamplingStrategies) Size() (n int) { if m == nil { return 0 } var l int _ = l if m.DefaultSamplingProbability != 0 { n += 9 } if m.DefaultLowerBoundTracesPerSecond != 0 { n += 9 } if len(m.PerOperationStrategies) > 0 { for _, e := range m.PerOperationStrategies { l = e.Size() n += 1 + l + sovSampling(uint64(l)) } } if m.DefaultUpperBoundTracesPerSecond != 0 { n += 9 } if m.XXX_unrecognized != nil { n += len(m.XXX_unrecognized) } return n } func (m *SamplingStrategyResponse) Size() (n int) { if m == nil { return 0 } var l int _ = l if m.StrategyType != 0 { n += 1 + sovSampling(uint64(m.StrategyType)) } if m.ProbabilisticSampling != nil { l = m.ProbabilisticSampling.Size() n += 1 + l + sovSampling(uint64(l)) } if m.RateLimitingSampling != nil { l = m.RateLimitingSampling.Size() n += 1 + l + sovSampling(uint64(l)) } if m.OperationSampling != nil { l = m.OperationSampling.Size() n += 1 + l + sovSampling(uint64(l)) } if m.XXX_unrecognized != nil { n += len(m.XXX_unrecognized) } return n } func (m *SamplingStrategyParameters) Size() (n int) { if m == nil { return 0 } var l int _ = l l = len(m.ServiceName) if l > 0 { n += 1 + l + sovSampling(uint64(l)) } if m.XXX_unrecognized != nil { n += len(m.XXX_unrecognized) } return n } func sovSampling(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } func sozSampling(x uint64) (n int) { return sovSampling(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } func (m *ProbabilisticSamplingStrategy) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx var wire uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowSampling } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ wire |= uint64(b&0x7F) << shift if b < 0x80 { break } } fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { return fmt.Errorf("proto: ProbabilisticSamplingStrategy: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: ProbabilisticSamplingStrategy: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 1 { return fmt.Errorf("proto: wrong wireType = %d for field SamplingRate", wireType) } var v uint64 if (iNdEx + 8) > l { return io.ErrUnexpectedEOF } v = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:])) iNdEx += 8 m.SamplingRate = float64(math.Float64frombits(v)) default: iNdEx = preIndex skippy, err := skipSampling(dAtA[iNdEx:]) if err != nil { return err } if (skippy < 0) || (iNdEx+skippy) < 0 { return ErrInvalidLengthSampling } if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF } m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) iNdEx += skippy } } if iNdEx > l { return io.ErrUnexpectedEOF } return nil } func (m *RateLimitingSamplingStrategy) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx var wire uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowSampling } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ wire |= uint64(b&0x7F) << shift if b < 0x80 { break } } fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { return fmt.Errorf("proto: RateLimitingSamplingStrategy: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: RateLimitingSamplingStrategy: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field MaxTracesPerSecond", wireType) } m.MaxTracesPerSecond = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowSampling } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ m.MaxTracesPerSecond |= int32(b&0x7F) << shift if b < 0x80 { break } } default: iNdEx = preIndex skippy, err := skipSampling(dAtA[iNdEx:]) if err != nil { return err } if (skippy < 0) || (iNdEx+skippy) < 0 { return ErrInvalidLengthSampling } if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF } m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) iNdEx += skippy } } if iNdEx > l { return io.ErrUnexpectedEOF } return nil } func (m *OperationSamplingStrategy) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx var wire uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowSampling } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ wire |= uint64(b&0x7F) << shift if b < 0x80 { break } } fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { return fmt.Errorf("proto: OperationSamplingStrategy: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: OperationSamplingStrategy: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Operation", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowSampling } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } intStringLen := int(stringLen) if intStringLen < 0 { return ErrInvalidLengthSampling } postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthSampling } if postIndex > l { return io.ErrUnexpectedEOF } m.Operation = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field ProbabilisticSampling", wireType) } var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowSampling } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ msglen |= int(b&0x7F) << shift if b < 0x80 { break } } if msglen < 0 { return ErrInvalidLengthSampling } postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthSampling } if postIndex > l { return io.ErrUnexpectedEOF } if m.ProbabilisticSampling == nil { m.ProbabilisticSampling = &ProbabilisticSamplingStrategy{} } if err := m.ProbabilisticSampling.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipSampling(dAtA[iNdEx:]) if err != nil { return err } if (skippy < 0) || (iNdEx+skippy) < 0 { return ErrInvalidLengthSampling } if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF } m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) iNdEx += skippy } } if iNdEx > l { return io.ErrUnexpectedEOF } return nil } func (m *PerOperationSamplingStrategies) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx var wire uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowSampling } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ wire |= uint64(b&0x7F) << shift if b < 0x80 { break } } fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { return fmt.Errorf("proto: PerOperationSamplingStrategies: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: PerOperationSamplingStrategies: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 1 { return fmt.Errorf("proto: wrong wireType = %d for field DefaultSamplingProbability", wireType) } var v uint64 if (iNdEx + 8) > l { return io.ErrUnexpectedEOF } v = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:])) iNdEx += 8 m.DefaultSamplingProbability = float64(math.Float64frombits(v)) case 2: if wireType != 1 { return fmt.Errorf("proto: wrong wireType = %d for field DefaultLowerBoundTracesPerSecond", wireType) } var v uint64 if (iNdEx + 8) > l { return io.ErrUnexpectedEOF } v = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:])) iNdEx += 8 m.DefaultLowerBoundTracesPerSecond = float64(math.Float64frombits(v)) case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field PerOperationStrategies", wireType) } var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowSampling } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ msglen |= int(b&0x7F) << shift if b < 0x80 { break } } if msglen < 0 { return ErrInvalidLengthSampling } postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthSampling } if postIndex > l { return io.ErrUnexpectedEOF } m.PerOperationStrategies = append(m.PerOperationStrategies, &OperationSamplingStrategy{}) if err := m.PerOperationStrategies[len(m.PerOperationStrategies)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 4: if wireType != 1 { return fmt.Errorf("proto: wrong wireType = %d for field DefaultUpperBoundTracesPerSecond", wireType) } var v uint64 if (iNdEx + 8) > l { return io.ErrUnexpectedEOF } v = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:])) iNdEx += 8 m.DefaultUpperBoundTracesPerSecond = float64(math.Float64frombits(v)) default: iNdEx = preIndex skippy, err := skipSampling(dAtA[iNdEx:]) if err != nil { return err } if (skippy < 0) || (iNdEx+skippy) < 0 { return ErrInvalidLengthSampling } if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF } m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) iNdEx += skippy } } if iNdEx > l { return io.ErrUnexpectedEOF } return nil } func (m *SamplingStrategyResponse) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx var wire uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowSampling } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ wire |= uint64(b&0x7F) << shift if b < 0x80 { break } } fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { return fmt.Errorf("proto: SamplingStrategyResponse: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: SamplingStrategyResponse: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field StrategyType", wireType) } m.StrategyType = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowSampling } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ m.StrategyType |= SamplingStrategyType(b&0x7F) << shift if b < 0x80 { break } } case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field ProbabilisticSampling", wireType) } var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowSampling } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ msglen |= int(b&0x7F) << shift if b < 0x80 { break } } if msglen < 0 { return ErrInvalidLengthSampling } postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthSampling } if postIndex > l { return io.ErrUnexpectedEOF } if m.ProbabilisticSampling == nil { m.ProbabilisticSampling = &ProbabilisticSamplingStrategy{} } if err := m.ProbabilisticSampling.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field RateLimitingSampling", wireType) } var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowSampling } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ msglen |= int(b&0x7F) << shift if b < 0x80 { break } } if msglen < 0 { return ErrInvalidLengthSampling } postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthSampling } if postIndex > l { return io.ErrUnexpectedEOF } if m.RateLimitingSampling == nil { m.RateLimitingSampling = &RateLimitingSamplingStrategy{} } if err := m.RateLimitingSampling.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 4: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field OperationSampling", wireType) } var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowSampling } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ msglen |= int(b&0x7F) << shift if b < 0x80 { break } } if msglen < 0 { return ErrInvalidLengthSampling } postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthSampling } if postIndex > l { return io.ErrUnexpectedEOF } if m.OperationSampling == nil { m.OperationSampling = &PerOperationSamplingStrategies{} } if err := m.OperationSampling.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipSampling(dAtA[iNdEx:]) if err != nil { return err } if (skippy < 0) || (iNdEx+skippy) < 0 { return ErrInvalidLengthSampling } if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF } m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) iNdEx += skippy } } if iNdEx > l { return io.ErrUnexpectedEOF } return nil } func (m *SamplingStrategyParameters) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx var wire uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowSampling } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ wire |= uint64(b&0x7F) << shift if b < 0x80 { break } } fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { return fmt.Errorf("proto: SamplingStrategyParameters: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: SamplingStrategyParameters: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field ServiceName", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowSampling } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } intStringLen := int(stringLen) if intStringLen < 0 { return ErrInvalidLengthSampling } postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthSampling } if postIndex > l { return io.ErrUnexpectedEOF } m.ServiceName = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipSampling(dAtA[iNdEx:]) if err != nil { return err } if (skippy < 0) || (iNdEx+skippy) < 0 { return ErrInvalidLengthSampling } if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF } m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) iNdEx += skippy } } if iNdEx > l { return io.ErrUnexpectedEOF } return nil } func skipSampling(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 depth := 0 for iNdEx < l { var wire uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return 0, ErrIntOverflowSampling } if iNdEx >= l { return 0, io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { break } } wireType := int(wire & 0x7) switch wireType { case 0: for shift := uint(0); ; shift += 7 { if shift >= 64 { return 0, ErrIntOverflowSampling } if iNdEx >= l { return 0, io.ErrUnexpectedEOF } iNdEx++ if dAtA[iNdEx-1] < 0x80 { break } } case 1: iNdEx += 8 case 2: var length int for shift := uint(0); ; shift += 7 { if shift >= 64 { return 0, ErrIntOverflowSampling } if iNdEx >= l { return 0, io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ length |= (int(b) & 0x7F) << shift if b < 0x80 { break } } if length < 0 { return 0, ErrInvalidLengthSampling } iNdEx += length case 3: depth++ case 4: if depth == 0 { return 0, ErrUnexpectedEndOfGroupSampling } depth-- case 5: iNdEx += 4 default: return 0, fmt.Errorf("proto: illegal wireType %d", wireType) } if iNdEx < 0 { return 0, ErrInvalidLengthSampling } if depth == 0 { return iNdEx, nil } } return 0, io.ErrUnexpectedEOF } var ( ErrInvalidLengthSampling = fmt.Errorf("proto: negative length found during unmarshaling") ErrIntOverflowSampling = fmt.Errorf("proto: integer overflow") ErrUnexpectedEndOfGroupSampling = fmt.Errorf("proto: unexpected end of group") ) open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/jaegerremote/internal/testutils/000077500000000000000000000000001470323427300324245ustar00rootroot00000000000000mock_agent.go000066400000000000000000000054351470323427300350120ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/jaegerremote/internal/testutils// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Copyright (c) 2021 The Jaeger Authors. // Copyright (c) 2017 Uber Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package testutils // import "go.opentelemetry.io/contrib/samplers/jaegerremote/internal/testutils" import ( "encoding/json" "fmt" "net/http" "net/http/httptest" ) // StartMockAgent runs a mock representation of jaeger-agent. // This function returns a started server. func StartMockAgent() (*MockAgent, error) { samplingManager := newSamplingManager() samplingHandler := &samplingHandler{manager: samplingManager} samplingServer := httptest.NewServer(samplingHandler) agent := &MockAgent{ samplingMgr: samplingManager, samplingSrv: samplingServer, } return agent, nil } // Close stops the serving of traffic. func (s *MockAgent) Close() { s.samplingSrv.Close() } // MockAgent is a mock representation of Jaeger Agent. // It has an HTTP endpoint for sampling strategies. type MockAgent struct { samplingMgr *samplingManager samplingSrv *httptest.Server } // SamplingServerAddr returns the host:port of HTTP server exposing sampling strategy endpoint. func (s *MockAgent) SamplingServerAddr() string { return s.samplingSrv.Listener.Addr().String() } // AddSamplingStrategy registers a sampling strategy for a service. func (s *MockAgent) AddSamplingStrategy(service string, strategy interface{}) { s.samplingMgr.AddSamplingStrategy(service, strategy) } type samplingHandler struct { manager *samplingManager } func (h *samplingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { services := r.URL.Query()["service"] if len(services) == 0 { http.Error(w, "'service' parameter is empty", http.StatusBadRequest) return } if len(services) > 1 { http.Error(w, "'service' parameter must occur only once", http.StatusBadRequest) return } resp, err := h.manager.GetSamplingStrategy(services[0]) if err != nil { http.Error(w, fmt.Sprintf("Error retrieving strategy: %+v", err), http.StatusInternalServerError) return } data, err := json.Marshal(resp) if err != nil { http.Error(w, "Cannot marshall Thrift to JSON", http.StatusInternalServerError) return } w.Header().Add("Content-Type", "application/json") if _, err := w.Write(data); err != nil { return } } mock_agent_test.go000066400000000000000000000043351470323427300360470ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/jaegerremote/internal/testutils// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Copyright (c) 2021 The Jaeger Authors. // Copyright (c) 2017 Uber Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package testutils import ( "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" jaeger_api_v2 "go.opentelemetry.io/contrib/samplers/jaegerremote/internal/proto-gen/jaeger-idl/proto/api_v2" "go.opentelemetry.io/contrib/samplers/jaegerremote/internal/utils" ) func TestMockAgentSamplingManager(t *testing.T) { mockAgent, err := StartMockAgent() require.NoError(t, err) defer mockAgent.Close() err = utils.GetJSON("http://"+mockAgent.SamplingServerAddr()+"/", nil) require.Error(t, err, "no 'service' parameter") err = utils.GetJSON("http://"+mockAgent.SamplingServerAddr()+"/?service=a&service=b", nil) require.Error(t, err, "Too many 'service' parameters") var resp jaeger_api_v2.SamplingStrategyResponse err = utils.GetJSON("http://"+mockAgent.SamplingServerAddr()+"/?service=something", &resp) require.NoError(t, err) assert.Equal(t, jaeger_api_v2.SamplingStrategyType_PROBABILISTIC, resp.StrategyType) mockAgent.AddSamplingStrategy("service123", &jaeger_api_v2.SamplingStrategyResponse{ StrategyType: jaeger_api_v2.SamplingStrategyType_RATE_LIMITING, RateLimitingSampling: &jaeger_api_v2.RateLimitingSamplingStrategy{ MaxTracesPerSecond: 123, }, }) err = utils.GetJSON("http://"+mockAgent.SamplingServerAddr()+"/?service=service123", &resp) require.NoError(t, err) assert.Equal(t, jaeger_api_v2.SamplingStrategyType_RATE_LIMITING, resp.StrategyType) require.NotNil(t, resp.RateLimitingSampling) assert.EqualValues(t, 123, resp.RateLimitingSampling.MaxTracesPerSecond) } sampling_manager.go000066400000000000000000000035531470323427300362060ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/jaegerremote/internal/testutils// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Copyright (c) 2021 The Jaeger Authors. // Copyright (c) 2017 Uber Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package testutils // import "go.opentelemetry.io/contrib/samplers/jaegerremote/internal/testutils" import ( "sync" jaeger_api_v2 "go.opentelemetry.io/contrib/samplers/jaegerremote/internal/proto-gen/jaeger-idl/proto/api_v2" ) func newSamplingManager() *samplingManager { return &samplingManager{ sampling: make(map[string]interface{}), } } type samplingManager struct { sampling map[string]interface{} mutex sync.Mutex } // GetSamplingStrategy implements handler method of sampling.SamplingManager. func (s *samplingManager) GetSamplingStrategy(serviceName string) (interface{}, error) { s.mutex.Lock() defer s.mutex.Unlock() if strategy, ok := s.sampling[serviceName]; ok { return strategy, nil } return &jaeger_api_v2.SamplingStrategyResponse{ StrategyType: jaeger_api_v2.SamplingStrategyType_PROBABILISTIC, ProbabilisticSampling: &jaeger_api_v2.ProbabilisticSamplingStrategy{ SamplingRate: 0.01, }, }, nil } // AddSamplingStrategy registers a sampling strategy for a service. func (s *samplingManager) AddSamplingStrategy(service string, strategy interface{}) { s.mutex.Lock() defer s.mutex.Unlock() s.sampling[service] = strategy } open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/jaegerremote/internal/utils/000077500000000000000000000000001470323427300315245ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/jaegerremote/internal/utils/http_json.go000066400000000000000000000032521470323427300340650ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Copyright (c) 2021 The Jaeger Authors. // Copyright (c) 2017 Uber Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package utils // import "go.opentelemetry.io/contrib/samplers/jaegerremote/internal/utils" import ( "encoding/json" "fmt" "io" "net/http" ) // GetJSON makes an HTTP call to the specified URL and parses the returned JSON into `out`. func GetJSON(url string, out interface{}) error { resp, err := http.Get(url) //nolint:gosec // False positive G107: Potential HTTP request made with variable url if err != nil { return err } return ReadJSON(resp, out) } // ReadJSON reads JSON from http.Response and parses it into `out`. func ReadJSON(resp *http.Response, out interface{}) error { defer resp.Body.Close() if resp.StatusCode >= 400 { body, err := io.ReadAll(resp.Body) if err != nil { return err } return fmt.Errorf("status code: %d, body: %s", resp.StatusCode, body) } if out == nil { _, err := io.Copy(io.Discard, resp.Body) if err != nil { return err } return nil } decoder := json.NewDecoder(resp.Body) return decoder.Decode(out) } http_json_test.go000066400000000000000000000033041470323427300350430ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/jaegerremote/internal/utils// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Copyright (c) 2021 The Jaeger Authors. // Copyright (c) 2017 Uber Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package utils import ( "net/http" "net/http/httptest" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) type testJSONStruct struct { Name string Age int } func TestGetJSON(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Add("Content-Type", "application/json") _, err := w.Write([]byte("{\"name\": \"Bender\", \"age\": 3}")) assert.NoError(t, err) })) defer server.Close() var s testJSONStruct err := GetJSON(server.URL, &s) require.NoError(t, err) assert.Equal(t, "Bender", s.Name) assert.Equal(t, 3, s.Age) } func TestGetJSONErrors(t *testing.T) { var s testJSONStruct err := GetJSON("localhost:0", &s) assert.Error(t, err) server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.Error(w, "some error", http.StatusInternalServerError) })) defer server.Close() err = GetJSON(server.URL, &s) assert.Error(t, err) } open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/jaegerremote/internal/utils/rate_limiter.go000066400000000000000000000101711470323427300345330ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Copyright (c) 2021 The Jaeger Authors. // Copyright (c) 2017 Uber Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package utils // import "go.opentelemetry.io/contrib/samplers/jaegerremote/internal/utils" import ( "sync" "time" ) // RateLimiter is a filter used to check if a message that is worth itemCost units is within the rate limits. // // RateLimiter is a rate limiter based on leaky bucket algorithm, formulated in terms of a // credits balance that is replenished every time CheckCredit() method is called (tick) by the amount proportional // to the time elapsed since the last tick, up to max of creditsPerSecond. A call to CheckCredit() takes a cost // of an item we want to pay with the balance. If the balance exceeds the cost of the item, the item is "purchased" // and the balance reduced, indicated by returned value of true. Otherwise the balance is unchanged and return false. // // This can be used to limit a rate of messages emitted by a service by instantiating the Rate Limiter with the // max number of messages a service is allowed to emit per second, and calling CheckCredit(1.0) for each message // to determine if the message is within the rate limit. // // It can also be used to limit the rate of traffic in bytes, by setting creditsPerSecond to desired throughput // as bytes/second, and calling CheckCredit() with the actual message size. type RateLimiter struct { lock sync.Mutex creditsPerSecond float64 balance float64 maxBalance float64 lastTick time.Time timeNow func() time.Time } // NewRateLimiter creates a new RateLimiter. func NewRateLimiter(creditsPerSecond, maxBalance float64) *RateLimiter { balance := maxBalance if creditsPerSecond == 0 { balance = 0 } return &RateLimiter{ creditsPerSecond: creditsPerSecond, balance: balance, maxBalance: maxBalance, lastTick: time.Now(), timeNow: time.Now, } } // CheckCredit tries to reduce the current balance by itemCost provided that the current balance // is not lest than itemCost. func (rl *RateLimiter) CheckCredit(itemCost float64) bool { rl.lock.Lock() defer rl.lock.Unlock() // if we have enough credits to pay for current item, then reduce balance and allow if rl.balance >= itemCost { rl.balance -= itemCost return true } // otherwise check if balance can be increased due to time elapsed, and try again rl.updateBalance() if rl.balance >= itemCost { rl.balance -= itemCost return true } return false } // updateBalance recalculates current balance based on time elapsed. Must be called while holding a lock. func (rl *RateLimiter) updateBalance() { // calculate how much time passed since the last tick, and update current tick currentTime := rl.timeNow() elapsedTime := currentTime.Sub(rl.lastTick) rl.lastTick = currentTime // calculate how much credit have we accumulated since the last tick rl.balance += elapsedTime.Seconds() * rl.creditsPerSecond if rl.balance > rl.maxBalance { rl.balance = rl.maxBalance } } // Update changes the main parameters of the rate limiter in-place, while retaining // the current accumulated balance (pro-rated to the new maxBalance value). Using this method // instead of creating a new rate limiter helps to avoid thundering herd when sampling // strategies are updated. func (rl *RateLimiter) Update(creditsPerSecond, maxBalance float64) { rl.lock.Lock() defer rl.lock.Unlock() rl.updateBalance() // get up to date balance rl.balance = rl.balance * maxBalance / rl.maxBalance rl.creditsPerSecond = creditsPerSecond rl.maxBalance = maxBalance } rate_limiter_test.go000066400000000000000000000062031470323427300355140ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/jaegerremote/internal/utils// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Copyright (c) 2021 The Jaeger Authors. // Copyright (c) 2017 Uber Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package utils import ( "testing" "time" "github.com/stretchr/testify/assert" ) func TestRateLimiter(t *testing.T) { rl := NewRateLimiter(2.0, 2.0) // stop time ts := time.Now() rl.lastTick = ts rl.timeNow = func() time.Time { return ts } assert.True(t, rl.CheckCredit(1.0)) assert.True(t, rl.CheckCredit(1.0)) assert.False(t, rl.CheckCredit(1.0)) // move time 250ms forward, not enough credits to pay for 1.0 item rl.timeNow = func() time.Time { return ts.Add(time.Second / 4) } assert.False(t, rl.CheckCredit(1.0)) // move time 500ms forward, now enough credits to pay for 1.0 item rl.timeNow = func() time.Time { return ts.Add(time.Second/4 + time.Second/2) } assert.True(t, rl.CheckCredit(1.0)) assert.False(t, rl.CheckCredit(1.0)) // move time 5s forward, enough to accumulate credits for 10 messages, but it should still be capped at 2 rl.lastTick = ts rl.timeNow = func() time.Time { return ts.Add(5 * time.Second) } assert.True(t, rl.CheckCredit(1.0)) assert.True(t, rl.CheckCredit(1.0)) assert.False(t, rl.CheckCredit(1.0)) assert.False(t, rl.CheckCredit(1.0)) assert.False(t, rl.CheckCredit(1.0)) } func TestRateLimiterMaxBalance(t *testing.T) { rl := NewRateLimiter(0.1, 1.0) // stop time ts := time.Now() rl.lastTick = ts rl.timeNow = func() time.Time { return ts } assert.True(t, rl.CheckCredit(1.0), "on initialization, should have enough credits for 1 message") // move time 20s forward, enough to accumulate credits for 2 messages, but it should still be capped at 1 rl.timeNow = func() time.Time { return ts.Add(time.Second * 20) } assert.True(t, rl.CheckCredit(1.0)) assert.False(t, rl.CheckCredit(1.0)) } func TestRateLimiterReconfigure(t *testing.T) { rl := NewRateLimiter(1, 1.0) assertBalance := func(expected float64) { const delta = 0.0000001 // just some precision for comparing floats assert.InDelta(t, expected, rl.balance, delta) } // stop time ts := time.Now() rl.lastTick = ts rl.timeNow = func() time.Time { return ts } assert.True(t, rl.CheckCredit(1.0), "first message must succeed") assert.False(t, rl.CheckCredit(1.0), "second message must be rejected") assertBalance(0.0) // move half-second forward rl.timeNow = func() time.Time { return ts.Add(time.Second / 2) } rl.updateBalance() assertBalance(0.5) // 50% of max rl.Update(2, 4) assertBalance(2) // 50% of max assert.EqualValues(t, 2, rl.creditsPerSecond) assert.EqualValues(t, 4, rl.maxBalance) } open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/jaegerremote/jaeger-idl/000077500000000000000000000000001470323427300305535ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/jaegerremote/jaeger-idl/proto/000077500000000000000000000000001470323427300317165ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/jaegerremote/jaeger-idl/proto/api_v2/000077500000000000000000000000001470323427300330765ustar00rootroot00000000000000sampling.proto000066400000000000000000000046441470323427300357260ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/jaegerremote/jaeger-idl/proto/api_v2// Copyright (c) 2019 The Jaeger Authors. // Copyright (c) 2018 Uber Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // This package implements the Jaeger jaeger-idl specification as defined // at https://github.com/jaegertracing/jaeger-idl/blob/main/proto/api_v2/sampling.proto syntax="proto3"; package jaeger.api_v2; import "gogoproto/gogo.proto"; import "google/api/annotations.proto"; option go_package = "api_v2"; option java_package = "io.jaegertracing.api_v2"; // Enable gogoprotobuf extensions (https://github.com/gogo/protobuf/blob/master/extensions.md). // Enable custom Marshal method. option (gogoproto.marshaler_all) = true; // Enable custom Unmarshal method. option (gogoproto.unmarshaler_all) = true; // Enable custom Size method (Required by Marshal and Unmarshal). option (gogoproto.sizer_all) = true; enum SamplingStrategyType { PROBABILISTIC = 0; RATE_LIMITING = 1; }; message ProbabilisticSamplingStrategy { double samplingRate = 1; } message RateLimitingSamplingStrategy { int32 maxTracesPerSecond = 1; } message OperationSamplingStrategy { string operation = 1; ProbabilisticSamplingStrategy probabilisticSampling = 2; } message PerOperationSamplingStrategies { double defaultSamplingProbability = 1; double defaultLowerBoundTracesPerSecond = 2; repeated OperationSamplingStrategy perOperationStrategies = 3; double defaultUpperBoundTracesPerSecond = 4; } message SamplingStrategyResponse { SamplingStrategyType strategyType = 1; ProbabilisticSamplingStrategy probabilisticSampling = 2; RateLimitingSamplingStrategy rateLimitingSampling = 3; PerOperationSamplingStrategies operationSampling =4; } message SamplingStrategyParameters { string serviceName = 1; } service SamplingManager { rpc GetSamplingStrategy(SamplingStrategyParameters) returns (SamplingStrategyResponse) { option (google.api.http) = { post: "/api/v2/samplingStrategy" body: "*" }; } } open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/jaegerremote/sampler.go000066400000000000000000000273031470323427300305470ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Copyright (c) 2021 The Jaeger Authors. // Copyright (c) 2017 Uber Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package jaegerremote // import "go.opentelemetry.io/contrib/samplers/jaegerremote" import ( "encoding/binary" "fmt" "math" "sync" jaeger_api_v2 "go.opentelemetry.io/contrib/samplers/jaegerremote/internal/proto-gen/jaeger-idl/proto/api_v2" "go.opentelemetry.io/contrib/samplers/jaegerremote/internal/utils" "go.opentelemetry.io/otel/sdk/trace" oteltrace "go.opentelemetry.io/otel/trace" ) const ( defaultMaxOperations = 2000 ) // ----------------------- // probabilisticSampler is a sampler that randomly samples a certain percentage // of traces. type probabilisticSampler struct { samplingRate float64 samplingBoundary uint64 } const maxRandomNumber = ^(uint64(1) << 63) // i.e. 0x7fffffffffffffff // newProbabilisticSampler creates a sampler that randomly samples a certain percentage of traces specified by the // samplingRate, in the range between 0.0 and 1.0. // // It relies on the fact that new trace IDs are 63bit random numbers themselves, thus making the sampling decision // without generating a new random number, but simply calculating if traceID < (samplingRate * 2^63). func newProbabilisticSampler(samplingRate float64) *probabilisticSampler { s := new(probabilisticSampler) return s.init(samplingRate) } func (s *probabilisticSampler) init(samplingRate float64) *probabilisticSampler { s.samplingRate = math.Max(0.0, math.Min(samplingRate, 1.0)) s.samplingBoundary = uint64(float64(maxRandomNumber) * s.samplingRate) return s } // SamplingRate returns the sampling probability this sampled was constructed with. func (s *probabilisticSampler) SamplingRate() float64 { return s.samplingRate } func (s *probabilisticSampler) ShouldSample(p trace.SamplingParameters) trace.SamplingResult { psc := oteltrace.SpanContextFromContext(p.ParentContext) traceID := binary.BigEndian.Uint64(p.TraceID[0:8]) if s.samplingBoundary >= traceID&maxRandomNumber { return trace.SamplingResult{ Decision: trace.RecordAndSample, Tracestate: psc.TraceState(), } } return trace.SamplingResult{ Decision: trace.Drop, Tracestate: psc.TraceState(), } } // Equal compares with another sampler. func (s *probabilisticSampler) Equal(other trace.Sampler) bool { if o, ok := other.(*probabilisticSampler); ok { return s.samplingBoundary == o.samplingBoundary } return false } // Update modifies in-place the sampling rate. Locking must be done externally. func (s *probabilisticSampler) Update(samplingRate float64) error { if samplingRate < 0.0 || samplingRate > 1.0 { return fmt.Errorf("sampling rate must be between 0.0 and 1.0, received %f", samplingRate) } s.init(samplingRate) return nil } func (s *probabilisticSampler) Description() string { return "probabilisticSampler{}" } // ----------------------- // rateLimitingSampler samples at most maxTracesPerSecond. The distribution of sampled traces follows // burstiness of the service, i.e. a service with uniformly distributed requests will have those // requests sampled uniformly as well, but if requests are bursty, especially sub-second, then a // number of sequential requests can be sampled each second. type rateLimitingSampler struct { maxTracesPerSecond float64 rateLimiter *utils.RateLimiter } // newRateLimitingSampler creates new rateLimitingSampler. func newRateLimitingSampler(maxTracesPerSecond float64) *rateLimitingSampler { s := new(rateLimitingSampler) return s.init(maxTracesPerSecond) } func (s *rateLimitingSampler) init(maxTracesPerSecond float64) *rateLimitingSampler { if s.rateLimiter == nil { s.rateLimiter = utils.NewRateLimiter(maxTracesPerSecond, math.Max(maxTracesPerSecond, 1.0)) } else { s.rateLimiter.Update(maxTracesPerSecond, math.Max(maxTracesPerSecond, 1.0)) } s.maxTracesPerSecond = maxTracesPerSecond return s } func (s *rateLimitingSampler) ShouldSample(p trace.SamplingParameters) trace.SamplingResult { psc := oteltrace.SpanContextFromContext(p.ParentContext) if s.rateLimiter.CheckCredit(1.0) { return trace.SamplingResult{ Decision: trace.RecordAndSample, Tracestate: psc.TraceState(), } } return trace.SamplingResult{ Decision: trace.Drop, Tracestate: psc.TraceState(), } } // Update reconfigures the rate limiter, while preserving its accumulated balance. // Locking must be done externally. func (s *rateLimitingSampler) Update(maxTracesPerSecond float64) { if s.maxTracesPerSecond != maxTracesPerSecond { s.init(maxTracesPerSecond) } } // Equal compares with another sampler. func (s *rateLimitingSampler) Equal(other trace.Sampler) bool { if o, ok := other.(*rateLimitingSampler); ok { return s.maxTracesPerSecond == o.maxTracesPerSecond } return false } func (s *rateLimitingSampler) Description() string { return "rateLimitingSampler{}" } // ----------------------- // guaranteedThroughputProbabilisticSampler is a sampler that leverages both probabilisticSampler and // rateLimitingSampler. The rateLimitingSampler is used as a guaranteed lower bound sampler such that // every operation is sampled at least once in a time interval defined by the lowerBound. ie a lowerBound // of 1.0 / (60 * 10) will sample an operation at least once every 10 minutes. // // The probabilisticSampler is given higher priority when tags are emitted, ie. if IsSampled() for both // samplers return true, the tags for probabilisticSampler will be used. type guaranteedThroughputProbabilisticSampler struct { probabilisticSampler *probabilisticSampler lowerBoundSampler *rateLimitingSampler samplingRate float64 lowerBound float64 } func newGuaranteedThroughputProbabilisticSampler(lowerBound, samplingRate float64) *guaranteedThroughputProbabilisticSampler { s := &guaranteedThroughputProbabilisticSampler{ lowerBoundSampler: newRateLimitingSampler(lowerBound), lowerBound: lowerBound, } s.setProbabilisticSampler(samplingRate) return s } func (s *guaranteedThroughputProbabilisticSampler) setProbabilisticSampler(samplingRate float64) { if s.probabilisticSampler == nil { s.probabilisticSampler = newProbabilisticSampler(samplingRate) } else if s.samplingRate != samplingRate { s.probabilisticSampler.init(samplingRate) } // since we don't validate samplingRate, sampler may have clamped it to [0, 1] interval s.samplingRate = s.probabilisticSampler.SamplingRate() } func (s *guaranteedThroughputProbabilisticSampler) ShouldSample(p trace.SamplingParameters) trace.SamplingResult { if result := s.probabilisticSampler.ShouldSample(p); result.Decision == trace.RecordAndSample { s.lowerBoundSampler.ShouldSample(p) return result } result := s.lowerBoundSampler.ShouldSample(p) return result } // this function should only be called while holding a Write lock. func (s *guaranteedThroughputProbabilisticSampler) update(lowerBound, samplingRate float64) { s.setProbabilisticSampler(samplingRate) if s.lowerBound != lowerBound { s.lowerBoundSampler.Update(lowerBound) s.lowerBound = lowerBound } } func (s *guaranteedThroughputProbabilisticSampler) Description() string { return "guaranteedThroughputProbabilisticSampler{}" } // ----------------------- // perOperationSampler is a delegating sampler that applies guaranteedThroughputProbabilisticSampler // on a per-operation basis. type perOperationSampler struct { sync.RWMutex samplers map[string]*guaranteedThroughputProbabilisticSampler defaultSampler *probabilisticSampler lowerBound float64 maxOperations int // see description in perOperationSamplerParams operationNameLateBinding bool } // perOperationSamplerParams defines parameters when creating perOperationSampler. type perOperationSamplerParams struct { // Max number of operations that will be tracked. Other operations will be given default strategy. MaxOperations int // Opt-in feature for applications that require late binding of span name via explicit call to SetOperationName. // When this feature is enabled, the sampler will return retryable=true from OnCreateSpan(), thus leaving // the sampling decision as non-final (and the span as writeable). This may lead to degraded performance // in applications that always provide the correct span name on oteltrace creation. // // For backwards compatibility this option is off by default. OperationNameLateBinding bool // Initial configuration of the sampling strategies (usually retrieved from the backend by Remote Sampler). Strategies *jaeger_api_v2.PerOperationSamplingStrategies } // newPerOperationSampler returns a new perOperationSampler. func newPerOperationSampler(params perOperationSamplerParams) *perOperationSampler { if params.MaxOperations <= 0 { params.MaxOperations = defaultMaxOperations } samplers := make(map[string]*guaranteedThroughputProbabilisticSampler) for _, strategy := range params.Strategies.PerOperationStrategies { sampler := newGuaranteedThroughputProbabilisticSampler( params.Strategies.DefaultLowerBoundTracesPerSecond, strategy.ProbabilisticSampling.SamplingRate, ) samplers[strategy.Operation] = sampler } return &perOperationSampler{ samplers: samplers, defaultSampler: newProbabilisticSampler(params.Strategies.DefaultSamplingProbability), lowerBound: params.Strategies.DefaultLowerBoundTracesPerSecond, maxOperations: params.MaxOperations, operationNameLateBinding: params.OperationNameLateBinding, } } func (s *perOperationSampler) ShouldSample(p trace.SamplingParameters) trace.SamplingResult { sampler := s.getSamplerForOperation(p.Name) return sampler.ShouldSample(p) } func (s *perOperationSampler) getSamplerForOperation(operation string) trace.Sampler { s.RLock() sampler, ok := s.samplers[operation] if ok { defer s.RUnlock() return sampler } s.RUnlock() s.Lock() defer s.Unlock() // Check if sampler has already been created sampler, ok = s.samplers[operation] if ok { return sampler } // Store only up to maxOperations of unique ops. if len(s.samplers) >= s.maxOperations { return s.defaultSampler } newSampler := newGuaranteedThroughputProbabilisticSampler(s.lowerBound, s.defaultSampler.SamplingRate()) s.samplers[operation] = newSampler return newSampler } func (s *perOperationSampler) Description() string { return "perOperationSampler{}" } func (s *perOperationSampler) update(strategies *jaeger_api_v2.PerOperationSamplingStrategies) { s.Lock() defer s.Unlock() newSamplers := map[string]*guaranteedThroughputProbabilisticSampler{} for _, strategy := range strategies.PerOperationStrategies { operation := strategy.Operation samplingRate := strategy.ProbabilisticSampling.SamplingRate lowerBound := strategies.DefaultLowerBoundTracesPerSecond if sampler, ok := s.samplers[operation]; ok { sampler.update(lowerBound, samplingRate) newSamplers[operation] = sampler } else { sampler := newGuaranteedThroughputProbabilisticSampler( lowerBound, samplingRate, ) newSamplers[operation] = sampler } } s.lowerBound = strategies.DefaultLowerBoundTracesPerSecond if s.defaultSampler.SamplingRate() != strategies.DefaultSamplingProbability { s.defaultSampler = newProbabilisticSampler(strategies.DefaultSamplingProbability) } s.samplers = newSamplers } open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/jaegerremote/sampler_remote.go000066400000000000000000000231601470323427300321170ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Copyright (c) 2021 The Jaeger Authors. // Copyright (c) 2017 Uber Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package jaegerremote // import "go.opentelemetry.io/contrib/samplers/jaegerremote" import ( "bytes" "fmt" "io" "net/http" "net/url" "sync" "sync/atomic" "time" "github.com/gogo/protobuf/jsonpb" jaeger_api_v2 "go.opentelemetry.io/contrib/samplers/jaegerremote/internal/proto-gen/jaeger-idl/proto/api_v2" "go.opentelemetry.io/otel/sdk/trace" ) const ( defaultRemoteSamplingTimeout = 10 * time.Second defaultSamplingRefreshInterval = time.Minute defaultSamplingMaxOperations = 256 defaultSamplingOperationNameLateBinding = true ) // SamplingStrategyFetcher is used to fetch sampling strategy updates from remote server. type SamplingStrategyFetcher interface { Fetch(service string) ([]byte, error) } // samplingStrategyParser is used to parse sampling strategy updates. The output object // should be of the type that is recognized by the SamplerUpdaters. type samplingStrategyParser interface { Parse(response []byte) (interface{}, error) } // samplerUpdater is used by Sampler to apply sampling strategies, // retrieved from remote config server, to the current sampler. The updater can modify // the sampler in-place if sampler supports it, or create a new one. // // If the strategy does not contain configuration for the sampler in question, // updater must return modifiedSampler=nil to give other updaters a chance to inspect // the sampling strategy response. // // Sampler invokes the updaters while holding a lock on the main sampler. type samplerUpdater interface { Update(sampler trace.Sampler, strategy interface{}) (modified trace.Sampler, err error) } // Sampler is a delegating sampler that polls a remote server // for the appropriate sampling strategy, constructs a corresponding sampler and // delegates to it for sampling decisions. type Sampler struct { // These fields must be first in the struct because `sync/atomic` expects 64-bit alignment. // Cf. https://github.com/uber/jaeger-client-go/issues/155, https://goo.gl/zW7dgq closed int64 // 0 - not closed, 1 - closed sync.RWMutex // used to serialize access to samplerConfig.sampler config serviceName string doneChan chan *sync.WaitGroup } // New creates a sampler that periodically pulls // the sampling strategy from an HTTP sampling server (e.g. jaeger-agent). func New( serviceName string, opts ...Option, ) *Sampler { options := newConfig(opts...) sampler := &Sampler{ config: options, serviceName: serviceName, doneChan: make(chan *sync.WaitGroup), } go sampler.pollController() return sampler } // ShouldSample returns a sampling choice based on the passed sampling // parameters. func (s *Sampler) ShouldSample(p trace.SamplingParameters) trace.SamplingResult { s.RLock() defer s.RUnlock() return s.sampler.ShouldSample(p) } // Close does a clean shutdown of the sampler, stopping any background // go-routines it may have started. func (s *Sampler) Close() { if swapped := atomic.CompareAndSwapInt64(&s.closed, 0, 1); !swapped { s.logger.Info("repeated attempt to close the sampler is ignored") return } var wg sync.WaitGroup wg.Add(1) s.doneChan <- &wg wg.Wait() } // Description returns a human-readable name for the Sampler. func (s *Sampler) Description() string { return "JaegerRemoteSampler{}" } func (s *Sampler) pollController() { ticker := time.NewTicker(s.samplingRefreshInterval) defer ticker.Stop() s.pollControllerWithTicker(ticker) } func (s *Sampler) pollControllerWithTicker(ticker *time.Ticker) { s.UpdateSampler() for { select { case <-ticker.C: s.UpdateSampler() case wg := <-s.doneChan: wg.Done() return } } } func (s *Sampler) setSampler(sampler trace.Sampler) { s.Lock() defer s.Unlock() s.sampler = sampler } // UpdateSampler forces the sampler to fetch sampling strategy from backend server. // This function is called automatically on a timer, but can also be safely called manually, e.g. from tests. func (s *Sampler) UpdateSampler() { res, err := s.samplingFetcher.Fetch(s.serviceName) if err != nil { s.logger.Error(err, "failed to fetch sampling strategy") return } strategy, err := s.samplingParser.Parse(res) if err != nil { s.logger.Error(err, "failed to parse sampling strategy response") return } s.Lock() defer s.Unlock() if err := s.updateSamplerViaUpdaters(strategy); err != nil { s.logger.Error(err, "failed to handle sampling strategy response", "response", res) return } } // NB: this function should only be called while holding a Write lock. func (s *Sampler) updateSamplerViaUpdaters(strategy interface{}) error { for _, updater := range s.updaters { sampler, err := updater.Update(s.sampler, strategy) if err != nil { return err } if sampler != nil { s.sampler = sampler return nil } } return fmt.Errorf("unsupported sampling strategy %+v", strategy) } // ----------------------- // probabilisticSamplerUpdater is used by Sampler to parse sampling configuration. type probabilisticSamplerUpdater struct{} // Update implements Update of samplerUpdater. func (u *probabilisticSamplerUpdater) Update(sampler trace.Sampler, strategy interface{}) (trace.Sampler, error) { type response interface { GetProbabilisticSampling() *jaeger_api_v2.ProbabilisticSamplingStrategy } var _ response = new(jaeger_api_v2.SamplingStrategyResponse) // sanity signature check if resp, ok := strategy.(response); ok { if probabilistic := resp.GetProbabilisticSampling(); probabilistic != nil { if ps, ok := sampler.(*probabilisticSampler); ok { if err := ps.Update(probabilistic.SamplingRate); err != nil { return nil, err } return sampler, nil } return newProbabilisticSampler(probabilistic.SamplingRate), nil } } return nil, nil } // ----------------------- // rateLimitingSamplerUpdater is used by Sampler to parse sampling configuration. type rateLimitingSamplerUpdater struct{} // Update implements Update of samplerUpdater. func (u *rateLimitingSamplerUpdater) Update(sampler trace.Sampler, strategy interface{}) (trace.Sampler, error) { type response interface { GetRateLimitingSampling() *jaeger_api_v2.RateLimitingSamplingStrategy } var _ response = new(jaeger_api_v2.SamplingStrategyResponse) // sanity signature check if resp, ok := strategy.(response); ok { if rateLimiting := resp.GetRateLimitingSampling(); rateLimiting != nil { rateLimit := float64(rateLimiting.MaxTracesPerSecond) if rl, ok := sampler.(*rateLimitingSampler); ok { rl.Update(rateLimit) return rl, nil } return newRateLimitingSampler(rateLimit), nil } } return nil, nil } // ----------------------- // perOperationSamplerUpdater is used by Sampler to parse sampling configuration. // Fields have the same meaning as in perOperationSamplerParams. type perOperationSamplerUpdater struct { MaxOperations int OperationNameLateBinding bool } // Update implements Update of samplerUpdater. func (u *perOperationSamplerUpdater) Update(sampler trace.Sampler, strategy interface{}) (trace.Sampler, error) { type response interface { GetOperationSampling() *jaeger_api_v2.PerOperationSamplingStrategies } var _ response = new(jaeger_api_v2.SamplingStrategyResponse) // sanity signature check if p, ok := strategy.(response); ok { if operations := p.GetOperationSampling(); operations != nil { if as, ok := sampler.(*perOperationSampler); ok { as.update(operations) return as, nil } return newPerOperationSampler(perOperationSamplerParams{ MaxOperations: u.MaxOperations, OperationNameLateBinding: u.OperationNameLateBinding, Strategies: operations, }), nil } } return nil, nil } // ----------------------- type httpSamplingStrategyFetcher struct { serverURL string httpClient http.Client } func newHTTPSamplingStrategyFetcher(serverURL string) *httpSamplingStrategyFetcher { return &httpSamplingStrategyFetcher{ serverURL: serverURL, httpClient: http.Client{ Timeout: defaultRemoteSamplingTimeout, }, } } func (f *httpSamplingStrategyFetcher) Fetch(serviceName string) ([]byte, error) { v := url.Values{} v.Set("service", serviceName) uri := f.serverURL + "?" + v.Encode() resp, err := f.httpClient.Get(uri) if err != nil { return nil, err } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return nil, err } if resp.StatusCode >= 400 { return nil, fmt.Errorf("status code: %d, body: %c", resp.StatusCode, body) } return body, nil } // ----------------------- type samplingStrategyParserImpl struct{} func (p *samplingStrategyParserImpl) Parse(response []byte) (interface{}, error) { strategy := new(jaeger_api_v2.SamplingStrategyResponse) // Official Jaeger Remote Sampling protocol contains enums encoded as strings. // Legacy protocol contains enums as numbers. // Gogo's jsonpb module can parse either format. // Cf. https://github.com/open-telemetry/opentelemetry-go-contrib/issues/3184 if err := jsonpb.Unmarshal(bytes.NewReader(response), strategy); err != nil { return nil, err } return strategy, nil } open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/jaegerremote/sampler_remote_options.go000066400000000000000000000117131470323427300336730ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Copyright (c) 2021 The Jaeger Authors. // Copyright (c) 2017 Uber Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package jaegerremote // import "go.opentelemetry.io/contrib/samplers/jaegerremote" import ( "time" "github.com/go-logr/logr" "go.opentelemetry.io/otel/sdk/trace" ) type config struct { sampler trace.Sampler samplingServerURL string samplingRefreshInterval time.Duration samplingFetcher SamplingStrategyFetcher samplingParser samplingStrategyParser updaters []samplerUpdater posParams perOperationSamplerParams logger logr.Logger } // newConfig returns an appropriately configured config. func newConfig(options ...Option) config { c := config{ sampler: newProbabilisticSampler(0.001), samplingServerURL: defaultSamplingServerURL, samplingRefreshInterval: defaultSamplingRefreshInterval, samplingFetcher: newHTTPSamplingStrategyFetcher(defaultSamplingServerURL), samplingParser: new(samplingStrategyParserImpl), updaters: []samplerUpdater{ new(probabilisticSamplerUpdater), new(rateLimitingSamplerUpdater), }, posParams: perOperationSamplerParams{ MaxOperations: defaultSamplingMaxOperations, OperationNameLateBinding: defaultSamplingOperationNameLateBinding, }, logger: logr.Discard(), } for _, option := range options { option.apply(&c) } c.updaters = append([]samplerUpdater{&perOperationSamplerUpdater{ MaxOperations: c.posParams.MaxOperations, OperationNameLateBinding: c.posParams.OperationNameLateBinding, }}, c.updaters...) return c } // Option applies configuration settings to a Sampler. type Option interface { apply(*config) } type optionFunc func(*config) func (fn optionFunc) apply(c *config) { fn(c) } // WithInitialSampler creates a Option that sets the initial sampler // to use before a remote sampler is created and used. func WithInitialSampler(sampler trace.Sampler) Option { return optionFunc(func(c *config) { c.sampler = sampler }) } // WithSamplingServerURL creates a Option that sets the sampling server url // of the local agent that contains the sampling strategies. func WithSamplingServerURL(samplingServerURL string) Option { return optionFunc(func(c *config) { c.samplingServerURL = samplingServerURL // The default port of jaeger agent is 5778, but there are other ports specified by the user, so the sampling address and fetch address are strongly bound c.samplingFetcher = newHTTPSamplingStrategyFetcher(samplingServerURL) }) } // WithMaxOperations creates a Option that sets the maximum number of // operations the sampler will keep track of. func WithMaxOperations(maxOperations int) Option { return optionFunc(func(c *config) { c.posParams.MaxOperations = maxOperations }) } // WithOperationNameLateBinding creates a Option that sets the respective // field in the perOperationSamplerParams. func WithOperationNameLateBinding(enable bool) Option { return optionFunc(func(c *config) { c.posParams.OperationNameLateBinding = enable }) } // WithSamplingRefreshInterval creates a Option that sets how often the // sampler will poll local agent for the appropriate sampling strategy. func WithSamplingRefreshInterval(samplingRefreshInterval time.Duration) Option { return optionFunc(func(c *config) { c.samplingRefreshInterval = samplingRefreshInterval }) } // WithLogger configures the sampler to log operation and debug information with logger. func WithLogger(logger logr.Logger) Option { return optionFunc(func(c *config) { c.logger = logger }) } // WithSamplingStrategyFetcher creates an Option that initializes the sampling strategy fetcher. // Custom fetcher can be used for setting custom headers, timeouts, etc., or getting // sampling strategies from a different source, like files. func WithSamplingStrategyFetcher(fetcher SamplingStrategyFetcher) Option { return optionFunc(func(c *config) { c.samplingFetcher = fetcher }) } // samplingStrategyParser creates a Option that initializes sampling strategy parser. func withSamplingStrategyParser(parser samplingStrategyParser) Option { return optionFunc(func(c *config) { c.samplingParser = parser }) } // withUpdaters creates a Option that initializes sampler updaters. func withUpdaters(updaters ...samplerUpdater) Option { return optionFunc(func(c *config) { c.updaters = updaters }) } open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/jaegerremote/sampler_remote_test.go000066400000000000000000000503621470323427300331620ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Copyright (c) 2021 The Jaeger Authors. // Copyright (c) 2017 Uber Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package jaegerremote import ( "encoding/binary" "errors" "fmt" "sync" "testing" "time" "github.com/go-logr/logr/testr" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" jaeger_api_v2 "go.opentelemetry.io/contrib/samplers/jaegerremote/internal/proto-gen/jaeger-idl/proto/api_v2" "go.opentelemetry.io/contrib/samplers/jaegerremote/internal/testutils" "go.opentelemetry.io/otel/sdk/trace" oteltrace "go.opentelemetry.io/otel/trace" ) func TestRemotelyControlledSampler_updateConcurrentSafe(t *testing.T) { initSampler := newProbabilisticSampler(0.123) fetcher := &testSamplingStrategyFetcher{response: []byte("probabilistic")} parser := new(testSamplingStrategyParser) updaters := []samplerUpdater{new(probabilisticSamplerUpdater)} sampler := New( "test", WithMaxOperations(42), WithOperationNameLateBinding(true), WithInitialSampler(initSampler), WithSamplingServerURL("my url"), WithSamplingRefreshInterval(time.Millisecond), WithSamplingStrategyFetcher(fetcher), withSamplingStrategyParser(parser), withUpdaters(updaters...), ) defer sampler.Close() s := makeSamplingParameters(1, "test") var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() sampler.UpdateSampler() }() wg.Add(1) go func() { defer wg.Done() sampler.ShouldSample(s) }() wg.Wait() } type testSamplingStrategyFetcher struct { response []byte } func (c *testSamplingStrategyFetcher) Fetch(serviceName string) ([]byte, error) { return c.response, nil } type testSamplingStrategyParser struct{} func (p *testSamplingStrategyParser) Parse(response []byte) (interface{}, error) { strategy := new(jaeger_api_v2.SamplingStrategyResponse) switch string(response) { case "probabilistic": strategy.StrategyType = jaeger_api_v2.SamplingStrategyType_PROBABILISTIC strategy.ProbabilisticSampling = &jaeger_api_v2.ProbabilisticSamplingStrategy{ SamplingRate: 0.85, } return strategy, nil case "rateLimiting": strategy.StrategyType = jaeger_api_v2.SamplingStrategyType_RATE_LIMITING strategy.RateLimitingSampling = &jaeger_api_v2.RateLimitingSamplingStrategy{ MaxTracesPerSecond: 100, } return strategy, nil } return nil, errors.New("unknown strategy test request") } func TestRemoteSamplerOptions(t *testing.T) { initSampler := newProbabilisticSampler(0.123) fetcher := new(fakeSamplingFetcher) parser := new(samplingStrategyParserImpl) logger := testr.New(t) updaters := []samplerUpdater{new(probabilisticSamplerUpdater)} sampler := New( "test", WithMaxOperations(42), WithOperationNameLateBinding(true), WithInitialSampler(initSampler), WithSamplingServerURL("my url"), WithSamplingRefreshInterval(42*time.Second), WithSamplingStrategyFetcher(fetcher), withSamplingStrategyParser(parser), withUpdaters(updaters...), WithLogger(logger), ) defer sampler.Close() assert.Equal(t, 42, sampler.posParams.MaxOperations) assert.True(t, sampler.posParams.OperationNameLateBinding) assert.Same(t, initSampler, sampler.sampler) assert.Equal(t, "my url", sampler.samplingServerURL) assert.Equal(t, 42*time.Second, sampler.samplingRefreshInterval) assert.Same(t, fetcher, sampler.samplingFetcher) assert.Same(t, parser, sampler.samplingParser) assert.EqualValues(t, &perOperationSamplerUpdater{MaxOperations: 42, OperationNameLateBinding: true}, sampler.updaters[0]) assert.Equal(t, logger, sampler.logger) } func TestRemoteSamplerOptionsDefaults(t *testing.T) { options := newConfig() sampler, ok := options.sampler.(*probabilisticSampler) assert.True(t, ok) assert.Equal(t, 0.001, sampler.samplingRate) assert.NotEmpty(t, options.samplingServerURL) assert.NotZero(t, options.samplingRefreshInterval) } func initAgent(t *testing.T) (*testutils.MockAgent, *Sampler) { agent, err := testutils.StartMockAgent() require.NoError(t, err) initialSampler := newProbabilisticSampler(0.001) sampler := New( "client app", WithSamplingServerURL("http://"+agent.SamplingServerAddr()), WithMaxOperations(testDefaultMaxOperations), WithInitialSampler(initialSampler), WithSamplingRefreshInterval(time.Minute), ) sampler.Close() // stop timer-based updates, we want to call them manually return agent, sampler } func makeSamplingParameters(id uint64, operationName string) trace.SamplingParameters { var traceID oteltrace.TraceID binary.BigEndian.PutUint64(traceID[:], id) return trace.SamplingParameters{ TraceID: traceID, Name: operationName, } } func TestRemotelyControlledSampler(t *testing.T) { agent, remoteSampler := initAgent(t) defer agent.Close() defaultSampler := newProbabilisticSampler(0.001) remoteSampler.setSampler(defaultSampler) agent.AddSamplingStrategy("client app", getSamplingStrategyResponse(jaeger_api_v2.SamplingStrategyType_PROBABILISTIC, testDefaultSamplingProbability)) remoteSampler.UpdateSampler() s1, ok := remoteSampler.sampler.(*probabilisticSampler) assert.True(t, ok) assert.EqualValues(t, testDefaultSamplingProbability, s1.samplingRate, "Sampler should have been updated") result := remoteSampler.ShouldSample(makeSamplingParameters(testMaxID+10, testOperationName)) assert.Equal(t, trace.Drop, result.Decision) result = remoteSampler.ShouldSample(makeSamplingParameters(testMaxID-10, testOperationName)) assert.Equal(t, trace.RecordAndSample, result.Decision) remoteSampler.setSampler(defaultSampler) c := make(chan time.Time) ticker := &time.Ticker{C: c} // reset closed so the next call to Close() correctly stops the polling goroutine remoteSampler.closed = 0 done := make(chan struct{}) go func() { defer close(done) remoteSampler.pollControllerWithTicker(ticker) }() c <- time.Now() // force update based on timer remoteSampler.Close() <-done s2, ok := remoteSampler.sampler.(*probabilisticSampler) assert.True(t, ok) assert.EqualValues(t, testDefaultSamplingProbability, s2.samplingRate, "Sampler should have been updated from timer") } func TestRemotelyControlledSampler_updateSampler(t *testing.T) { tests := []struct { probabilities map[string]float64 defaultProbability float64 expectedDefaultProbability float64 }{ { probabilities: map[string]float64{testOperationName: 1.1}, defaultProbability: testDefaultSamplingProbability, expectedDefaultProbability: testDefaultSamplingProbability, }, { probabilities: map[string]float64{testOperationName: testDefaultSamplingProbability}, defaultProbability: testDefaultSamplingProbability, expectedDefaultProbability: testDefaultSamplingProbability, }, { probabilities: map[string]float64{ testOperationName: testDefaultSamplingProbability, testFirstTimeOperationName: testDefaultSamplingProbability, }, defaultProbability: testDefaultSamplingProbability, expectedDefaultProbability: testDefaultSamplingProbability, }, { probabilities: map[string]float64{"new op": 1.1}, defaultProbability: testDefaultSamplingProbability, expectedDefaultProbability: testDefaultSamplingProbability, }, { probabilities: map[string]float64{"new op": 1.1}, defaultProbability: 1.1, expectedDefaultProbability: 1.0, }, } for i, test := range tests { t.Run(fmt.Sprintf("test_%d", i), func(t *testing.T) { agent, sampler := initAgent(t) defer agent.Close() initSampler, ok := sampler.sampler.(*probabilisticSampler) assert.True(t, ok) res := &jaeger_api_v2.SamplingStrategyResponse{ StrategyType: jaeger_api_v2.SamplingStrategyType_PROBABILISTIC, OperationSampling: &jaeger_api_v2.PerOperationSamplingStrategies{ DefaultSamplingProbability: test.defaultProbability, DefaultLowerBoundTracesPerSecond: 0.001, }, } for opName, prob := range test.probabilities { res.OperationSampling.PerOperationStrategies = append(res.OperationSampling.PerOperationStrategies, &jaeger_api_v2.OperationSamplingStrategy{ Operation: opName, ProbabilisticSampling: &jaeger_api_v2.ProbabilisticSamplingStrategy{ SamplingRate: prob, }, }, ) } agent.AddSamplingStrategy("client app", res) sampler.UpdateSampler() s, ok := sampler.sampler.(*perOperationSampler) assert.True(t, ok) assert.NotEqual(t, initSampler, sampler.sampler, "Sampler should have been updated") assert.Equal(t, test.expectedDefaultProbability, s.defaultSampler.SamplingRate()) // First call is always sampled result := sampler.ShouldSample(makeSamplingParameters(testMaxID+10, testOperationName)) assert.Equal(t, trace.RecordAndSample, result.Decision) result = sampler.ShouldSample(makeSamplingParameters(testMaxID-10, testOperationName)) assert.Equal(t, trace.RecordAndSample, result.Decision) }) } } func TestRemotelyControlledSampler_ImmediatelyUpdateOnStartup(t *testing.T) { initSampler := newProbabilisticSampler(0.123) fetcher := &testSamplingStrategyFetcher{response: []byte("rateLimiting")} parser := new(testSamplingStrategyParser) updaters := []samplerUpdater{new(probabilisticSamplerUpdater), new(rateLimitingSamplerUpdater)} sampler := New( "test", WithMaxOperations(42), WithOperationNameLateBinding(true), WithInitialSampler(initSampler), WithSamplingServerURL("my url"), WithSamplingRefreshInterval(10*time.Minute), WithSamplingStrategyFetcher(fetcher), withSamplingStrategyParser(parser), withUpdaters(updaters...), ) time.Sleep(100 * time.Millisecond) // waiting for s.pollController sampler.Close() // stop pollController, avoid date race s, ok := sampler.sampler.(*rateLimitingSampler) assert.True(t, ok) assert.Equal(t, float64(100), s.maxTracesPerSecond) } func TestRemotelyControlledSampler_multiStrategyResponse(t *testing.T) { agent, sampler := initAgent(t) defer agent.Close() initSampler, ok := sampler.sampler.(*probabilisticSampler) assert.True(t, ok) defaultSampingRate := 1.0 testUnusedOpName := "unused_op" testUnusedOpSamplingRate := 0.0 res := &jaeger_api_v2.SamplingStrategyResponse{ StrategyType: jaeger_api_v2.SamplingStrategyType_PROBABILISTIC, ProbabilisticSampling: &jaeger_api_v2.ProbabilisticSamplingStrategy{SamplingRate: defaultSampingRate}, OperationSampling: &jaeger_api_v2.PerOperationSamplingStrategies{ DefaultSamplingProbability: defaultSampingRate, DefaultLowerBoundTracesPerSecond: 0.001, PerOperationStrategies: []*jaeger_api_v2.OperationSamplingStrategy{ { Operation: testUnusedOpName, ProbabilisticSampling: &jaeger_api_v2.ProbabilisticSamplingStrategy{ SamplingRate: testUnusedOpSamplingRate, }, }, }, }, } agent.AddSamplingStrategy("client app", res) sampler.UpdateSampler() s, ok := sampler.sampler.(*perOperationSampler) assert.True(t, ok) assert.NotEqual(t, initSampler, sampler.sampler, "Sampler should have been updated") assert.Equal(t, defaultSampingRate, s.defaultSampler.SamplingRate()) result := sampler.ShouldSample(makeSamplingParameters(testMaxID-10, testUnusedOpName)) assert.Equal(t, trace.RecordAndSample, result.Decision) // first call always pass result = sampler.ShouldSample(makeSamplingParameters(testMaxID, testUnusedOpName)) assert.Equal(t, trace.Drop, result.Decision) } func TestSamplerQueryError(t *testing.T) { agent, sampler := initAgent(t) defer agent.Close() // override the actual handler sampler.samplingFetcher = &fakeSamplingFetcher{} initSampler, ok := sampler.sampler.(*probabilisticSampler) assert.True(t, ok) sampler.Close() // stop timer-based updates, we want to call them manually sampler.UpdateSampler() assert.Equal(t, initSampler, sampler.sampler, "Sampler should not have been updated due to query error") } type fakeSamplingFetcher struct{} func (c *fakeSamplingFetcher) Fetch(serviceName string) ([]byte, error) { return nil, errors.New("query error") } func TestRemotelyControlledSampler_updateSamplerFromAdaptiveSampler(t *testing.T) { agent, remoteSampler := initAgent(t) defer agent.Close() remoteSampler.Close() // close the second time (initAgent already called Close) strategies := &jaeger_api_v2.PerOperationSamplingStrategies{ DefaultSamplingProbability: testDefaultSamplingProbability, DefaultLowerBoundTracesPerSecond: 1.0, } adaptiveSampler := newPerOperationSampler(perOperationSamplerParams{ MaxOperations: testDefaultMaxOperations, Strategies: strategies, }) // Overwrite the sampler with an adaptive sampler remoteSampler.setSampler(adaptiveSampler) agent.AddSamplingStrategy("client app", getSamplingStrategyResponse(jaeger_api_v2.SamplingStrategyType_PROBABILISTIC, 0.5)) remoteSampler.UpdateSampler() // Sampler should have been updated to probabilistic _, ok := remoteSampler.sampler.(*probabilisticSampler) require.True(t, ok) // Overwrite the sampler with an adaptive sampler remoteSampler.setSampler(adaptiveSampler) agent.AddSamplingStrategy("client app", getSamplingStrategyResponse(jaeger_api_v2.SamplingStrategyType_RATE_LIMITING, 1)) remoteSampler.UpdateSampler() // Sampler should have been updated to ratelimiting _, ok = remoteSampler.sampler.(*rateLimitingSampler) require.True(t, ok) // Overwrite the sampler with an adaptive sampler remoteSampler.setSampler(adaptiveSampler) // Update existing adaptive sampler agent.AddSamplingStrategy("client app", &jaeger_api_v2.SamplingStrategyResponse{OperationSampling: strategies}) remoteSampler.UpdateSampler() } func TestRemotelyControlledSampler_updateRateLimitingOrProbabilisticSampler(t *testing.T) { probabilisticSampler := newProbabilisticSampler(0.002) otherProbabilisticSampler := newProbabilisticSampler(0.003) maxProbabilisticSampler := newProbabilisticSampler(1.0) rateLimitingSampler := newRateLimitingSampler(2) otherRateLimitingSampler := newRateLimitingSampler(3) testCases := []struct { res *jaeger_api_v2.SamplingStrategyResponse initSampler trace.Sampler expectedSampler trace.Sampler shouldErr bool referenceEquivalence bool caption string }{ { res: getSamplingStrategyResponse(jaeger_api_v2.SamplingStrategyType_PROBABILISTIC, 1.5), initSampler: probabilisticSampler, expectedSampler: maxProbabilisticSampler, shouldErr: true, referenceEquivalence: false, caption: "invalid probabilistic strategy", }, { res: getSamplingStrategyResponse(jaeger_api_v2.SamplingStrategyType_PROBABILISTIC, 0.002), initSampler: probabilisticSampler, expectedSampler: probabilisticSampler, shouldErr: false, referenceEquivalence: true, caption: "unchanged probabilistic strategy", }, { res: getSamplingStrategyResponse(jaeger_api_v2.SamplingStrategyType_PROBABILISTIC, 0.003), initSampler: probabilisticSampler, expectedSampler: otherProbabilisticSampler, shouldErr: false, referenceEquivalence: false, caption: "valid probabilistic strategy", }, { res: getSamplingStrategyResponse(jaeger_api_v2.SamplingStrategyType_RATE_LIMITING, 2), initSampler: rateLimitingSampler, expectedSampler: rateLimitingSampler, shouldErr: false, referenceEquivalence: true, caption: "unchanged rate limiting strategy", }, { res: getSamplingStrategyResponse(jaeger_api_v2.SamplingStrategyType_RATE_LIMITING, 3), initSampler: rateLimitingSampler, expectedSampler: otherRateLimitingSampler, shouldErr: false, referenceEquivalence: false, caption: "valid rate limiting strategy", }, { res: &jaeger_api_v2.SamplingStrategyResponse{}, initSampler: rateLimitingSampler, expectedSampler: rateLimitingSampler, shouldErr: true, referenceEquivalence: true, caption: "invalid strategy", }, } for _, tc := range testCases { testCase := tc // capture loop var t.Run(testCase.caption, func(t *testing.T) { remoteSampler := New( "test", WithInitialSampler(testCase.initSampler), withUpdaters( new(probabilisticSamplerUpdater), new(rateLimitingSamplerUpdater), ), ) defer remoteSampler.Close() err := remoteSampler.updateSamplerViaUpdaters(testCase.res) if testCase.shouldErr { require.Error(t, err) return } if testCase.referenceEquivalence { assert.Equal(t, testCase.expectedSampler, remoteSampler.sampler) } else { type comparable interface { Equal(other trace.Sampler) bool } es, esOk := testCase.expectedSampler.(comparable) require.True(t, esOk, "expected sampler %+v must implement Equal()", testCase.expectedSampler) assert.True(t, es.Equal(remoteSampler.sampler), "sampler.Equal: want=%+v, have=%+v", testCase.expectedSampler, remoteSampler.sampler) } }) } } func getSamplingStrategyResponse(strategyType jaeger_api_v2.SamplingStrategyType, value float64) *jaeger_api_v2.SamplingStrategyResponse { if strategyType == jaeger_api_v2.SamplingStrategyType_PROBABILISTIC { return &jaeger_api_v2.SamplingStrategyResponse{ StrategyType: jaeger_api_v2.SamplingStrategyType_PROBABILISTIC, ProbabilisticSampling: &jaeger_api_v2.ProbabilisticSamplingStrategy{ SamplingRate: value, }, } } if strategyType == jaeger_api_v2.SamplingStrategyType_RATE_LIMITING { return &jaeger_api_v2.SamplingStrategyResponse{ StrategyType: jaeger_api_v2.SamplingStrategyType_RATE_LIMITING, RateLimitingSampling: &jaeger_api_v2.RateLimitingSamplingStrategy{ MaxTracesPerSecond: int32(value), }, } } return nil } func TestSamplingStrategyParserImpl(t *testing.T) { assertProbabilistic := func(t *testing.T, s *jaeger_api_v2.SamplingStrategyResponse) { require.NotNil(t, s.GetProbabilisticSampling(), "output: %+v", s) require.EqualValues(t, 0.42, s.GetProbabilisticSampling().GetSamplingRate(), "output: %+v", s) } assertRateLimiting := func(t *testing.T, s *jaeger_api_v2.SamplingStrategyResponse) { require.NotNil(t, s.GetRateLimitingSampling(), "output: %+v", s) require.EqualValues(t, 42, s.GetRateLimitingSampling().GetMaxTracesPerSecond(), "output: %+v", s) } tests := []struct { name string json string assert func(t *testing.T, s *jaeger_api_v2.SamplingStrategyResponse) }{ { name: "official JSON probabilistic", json: `{"strategyType":"PROBABILISTIC","probabilisticSampling":{"samplingRate":0.42}}`, assert: assertProbabilistic, }, { name: "official JSON rate limiting", json: `{"strategyType":"RATE_LIMITING","rateLimitingSampling":{"maxTracesPerSecond":42}}`, assert: assertRateLimiting, }, { name: "legacy JSON probabilistic", json: `{"strategyType":0,"probabilisticSampling":{"samplingRate":0.42}}`, assert: assertProbabilistic, }, { name: "legacy JSON rate limiting", json: `{"strategyType":1,"rateLimitingSampling":{"maxTracesPerSecond":42}}`, assert: assertRateLimiting, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { val, err := new(samplingStrategyParserImpl).Parse([]byte(test.json)) require.NoError(t, err) s := val.(*jaeger_api_v2.SamplingStrategyResponse) test.assert(t, s) }) } } func TestSamplingStrategyParserImpl_Error(t *testing.T) { json := `{"strategyType":"foo_bar","probabilisticSampling":{"samplingRate":0.42}}` val, err := new(samplingStrategyParserImpl).Parse([]byte(json)) require.Error(t, err, "output: %+v", val) require.Contains(t, err.Error(), `unknown value "foo_bar"`) } func TestDefaultSamplingStrategyFetcher_Timeout(t *testing.T) { fetcher := newHTTPSamplingStrategyFetcher("") assert.Equal(t, defaultRemoteSamplingTimeout, fetcher.httpClient.Timeout) } open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/jaegerremote/sampler_test.go000066400000000000000000000212401470323427300316000ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Copyright (c) 2021 The Jaeger Authors. // Copyright (c) 2017 Uber Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package jaegerremote import ( "encoding/binary" "testing" "github.com/stretchr/testify/assert" jaeger_api_v2 "go.opentelemetry.io/contrib/samplers/jaegerremote/internal/proto-gen/jaeger-idl/proto/api_v2" "go.opentelemetry.io/otel/sdk/trace" oteltrace "go.opentelemetry.io/otel/trace" ) const ( testOperationName = "op" testFirstTimeOperationName = "firstTimeOp" testDefaultSamplingProbability = 0.5 testMaxID = uint64(1) << 62 testDefaultMaxOperations = 10 ) func TestProbabilisticSampler(t *testing.T) { var traceID oteltrace.TraceID sampler := newProbabilisticSampler(0.5) binary.BigEndian.PutUint64(traceID[:], testMaxID+10) result := sampler.ShouldSample(trace.SamplingParameters{TraceID: traceID}) assert.Equal(t, trace.Drop, result.Decision) binary.BigEndian.PutUint64(traceID[:], testMaxID-20) result = sampler.ShouldSample(trace.SamplingParameters{TraceID: traceID}) assert.Equal(t, trace.RecordAndSample, result.Decision) t.Run("test_64bit_id", func(t *testing.T) { binary.BigEndian.PutUint64(traceID[:], (testMaxID+10)|1<<63) result = sampler.ShouldSample(trace.SamplingParameters{TraceID: traceID}) assert.Equal(t, trace.Drop, result.Decision) binary.BigEndian.PutUint64(traceID[:], (testMaxID-20)|1<<63) result = sampler.ShouldSample(trace.SamplingParameters{TraceID: traceID}) assert.Equal(t, trace.RecordAndSample, result.Decision) }) } func TestRateLimitingSampler(t *testing.T) { sampler := newRateLimitingSampler(2) result := sampler.ShouldSample(trace.SamplingParameters{Name: testOperationName}) assert.Equal(t, trace.RecordAndSample, result.Decision) result = sampler.ShouldSample(trace.SamplingParameters{Name: testOperationName}) assert.Equal(t, trace.RecordAndSample, result.Decision) result = sampler.ShouldSample(trace.SamplingParameters{Name: testOperationName}) assert.Equal(t, trace.Drop, result.Decision) sampler = newRateLimitingSampler(0.1) result = sampler.ShouldSample(trace.SamplingParameters{Name: testOperationName}) assert.Equal(t, trace.RecordAndSample, result.Decision) result = sampler.ShouldSample(trace.SamplingParameters{Name: testOperationName}) assert.Equal(t, trace.Drop, result.Decision) sampler = newRateLimitingSampler(0) result = sampler.ShouldSample(trace.SamplingParameters{Name: testOperationName}) assert.Equal(t, trace.Drop, result.Decision) } func TestGuaranteedThroughputProbabilisticSamplerUpdate(t *testing.T) { samplingRate := 0.5 lowerBound := 2.0 sampler := newGuaranteedThroughputProbabilisticSampler(lowerBound, samplingRate) assert.Equal(t, lowerBound, sampler.lowerBound) assert.Equal(t, samplingRate, sampler.samplingRate) newSamplingRate := 0.6 newLowerBound := 1.0 sampler.update(newLowerBound, newSamplingRate) assert.Equal(t, newLowerBound, sampler.lowerBound) assert.Equal(t, newSamplingRate, sampler.samplingRate) newSamplingRate = 1.1 sampler.update(newLowerBound, newSamplingRate) assert.Equal(t, 1.0, sampler.samplingRate) } func TestAdaptiveSampler(t *testing.T) { samplingRates := []*jaeger_api_v2.OperationSamplingStrategy{ { Operation: testOperationName, ProbabilisticSampling: &jaeger_api_v2.ProbabilisticSamplingStrategy{SamplingRate: testDefaultSamplingProbability}, }, } strategies := &jaeger_api_v2.PerOperationSamplingStrategies{ DefaultSamplingProbability: testDefaultSamplingProbability, DefaultLowerBoundTracesPerSecond: 1.0, PerOperationStrategies: samplingRates, } sampler := newPerOperationSampler(perOperationSamplerParams{ Strategies: strategies, MaxOperations: 42, }) assert.Equal(t, 42, sampler.maxOperations) sampler = newPerOperationSampler(perOperationSamplerParams{Strategies: strategies}) assert.Equal(t, 2000, sampler.maxOperations, "default MaxOperations applied") sampler = newPerOperationSampler(perOperationSamplerParams{ MaxOperations: testDefaultMaxOperations, Strategies: strategies, }) result := sampler.ShouldSample(makeSamplingParameters(testMaxID+10, testOperationName)) assert.Equal(t, trace.RecordAndSample, result.Decision) result = sampler.ShouldSample(makeSamplingParameters(testMaxID-20, testOperationName)) assert.Equal(t, trace.RecordAndSample, result.Decision) result = sampler.ShouldSample(makeSamplingParameters(testMaxID+10, testOperationName)) assert.Equal(t, trace.Drop, result.Decision) // This operation is seen for the first time by the sampler result = sampler.ShouldSample(makeSamplingParameters(testMaxID, testFirstTimeOperationName)) assert.Equal(t, trace.RecordAndSample, result.Decision) } func TestAdaptiveSamplerErrors(t *testing.T) { strategies := &jaeger_api_v2.PerOperationSamplingStrategies{ DefaultSamplingProbability: testDefaultSamplingProbability, DefaultLowerBoundTracesPerSecond: 2.0, PerOperationStrategies: []*jaeger_api_v2.OperationSamplingStrategy{ { Operation: testOperationName, ProbabilisticSampling: &jaeger_api_v2.ProbabilisticSamplingStrategy{SamplingRate: -0.1}, }, }, } sampler := newPerOperationSampler(perOperationSamplerParams{ MaxOperations: testDefaultMaxOperations, Strategies: strategies, }) assert.Equal(t, 0.0, sampler.samplers[testOperationName].samplingRate) strategies.PerOperationStrategies[0].ProbabilisticSampling.SamplingRate = 1.1 sampler = newPerOperationSampler(perOperationSamplerParams{ MaxOperations: testDefaultMaxOperations, Strategies: strategies, }) assert.Equal(t, 1.0, sampler.samplers[testOperationName].samplingRate) } func TestAdaptiveSamplerUpdate(t *testing.T) { samplingRate := 0.1 lowerBound := 2.0 samplingRates := []*jaeger_api_v2.OperationSamplingStrategy{ { Operation: testOperationName, ProbabilisticSampling: &jaeger_api_v2.ProbabilisticSamplingStrategy{SamplingRate: samplingRate}, }, } strategies := &jaeger_api_v2.PerOperationSamplingStrategies{ DefaultSamplingProbability: testDefaultSamplingProbability, DefaultLowerBoundTracesPerSecond: lowerBound, PerOperationStrategies: samplingRates, } sampler := newPerOperationSampler(perOperationSamplerParams{ MaxOperations: testDefaultMaxOperations, Strategies: strategies, }) assert.Equal(t, lowerBound, sampler.lowerBound) assert.Equal(t, testDefaultSamplingProbability, sampler.defaultSampler.SamplingRate()) assert.Len(t, sampler.samplers, 1) // Update the sampler with new sampling rates newSamplingRate := 0.2 newLowerBound := 3.0 newDefaultSamplingProbability := 0.1 newSamplingRates := []*jaeger_api_v2.OperationSamplingStrategy{ { Operation: testOperationName, ProbabilisticSampling: &jaeger_api_v2.ProbabilisticSamplingStrategy{SamplingRate: newSamplingRate}, }, { Operation: testFirstTimeOperationName, ProbabilisticSampling: &jaeger_api_v2.ProbabilisticSamplingStrategy{SamplingRate: newSamplingRate}, }, } strategies = &jaeger_api_v2.PerOperationSamplingStrategies{ DefaultSamplingProbability: newDefaultSamplingProbability, DefaultLowerBoundTracesPerSecond: newLowerBound, PerOperationStrategies: newSamplingRates, } sampler.update(strategies) assert.Equal(t, newLowerBound, sampler.lowerBound) assert.Equal(t, newDefaultSamplingProbability, sampler.defaultSampler.SamplingRate()) assert.Len(t, sampler.samplers, 2) } func TestMaxOperations(t *testing.T) { samplingRates := []*jaeger_api_v2.OperationSamplingStrategy{ { Operation: testOperationName, ProbabilisticSampling: &jaeger_api_v2.ProbabilisticSamplingStrategy{SamplingRate: 0.1}, }, } strategies := &jaeger_api_v2.PerOperationSamplingStrategies{ DefaultSamplingProbability: testDefaultSamplingProbability, DefaultLowerBoundTracesPerSecond: 2.0, PerOperationStrategies: samplingRates, } sampler := newPerOperationSampler(perOperationSamplerParams{ MaxOperations: 1, Strategies: strategies, }) result := sampler.ShouldSample(makeSamplingParameters(testMaxID-10, testFirstTimeOperationName)) assert.Equal(t, trace.RecordAndSample, result.Decision) } open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/jaegerremote/version.go000066400000000000000000000010051470323427300305600ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package jaegerremote // import "go.opentelemetry.io/contrib/samplers/jaegerremote" // Version is the current release version of the Jaeger remote sampler. func Version() string { return "0.25.0" // This string is updated by the pre_release.sh script during release } // SemVersion is the semantic version to be supplied to tracer/meter creation. // // Deprecated: Use [Version] instead. func SemVersion() string { return Version() } open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/probability/000077500000000000000000000000001470323427300264175ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/probability/consistent/000077500000000000000000000000001470323427300306105ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/probability/consistent/base2.go000066400000000000000000000046161470323427300321420ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package consistent // import "go.opentelemetry.io/contrib/samplers/probability/consistent" import "math" // These are IEEE 754 double-width floating point constants used with // math.Float64bits. const ( offsetExponentMask = 0x7ff0000000000000 offsetExponentBias = 1023 significandBits = 52 ) // expFromFloat64 returns floor(log2(x)). func expFromFloat64(x float64) int { biased := (math.Float64bits(x) & offsetExponentMask) >> significandBits // The biased exponent can only be expressed with 11 bits (size (i.e. 64) - // significant (i.e 52) - sign (i.e. 1)). Meaning the int conversion below // is guaranteed to be lossless. return int(biased) - offsetExponentBias // nolint: gosec // See above comment. } // expToFloat64 returns 2^x. func expToFloat64(x int) float64 { // The exponent field is an 11-bit unsigned integer from 0 to 2047, in // biased form: an exponent value of 1023 represents the actual zero. // Exponents range from -1022 to +1023 because exponents of -1023 (all 0s) // and +1024 (all 1s) are reserved for special numbers. const low, high = -1022, 1023 if x < low { x = low } if x > high { x = high } biased := uint64(offsetExponentBias + x) // nolint: gosec // See comment and guard above. return math.Float64frombits(biased << significandBits) } // splitProb returns the two values of log-adjusted-count nearest to p // Example: // // splitProb(0.375) => (2, 1, 0.5) // // indicates to sample with probability (2^-2) 50% of the time // and (2^-1) 50% of the time. func splitProb(p float64) (uint8, uint8, float64) { if p < 2e-62 { // Note: spec. return pZeroValue, pZeroValue, 1 } // Take the exponent and drop the significand to locate the // smaller of two powers of two. exp := expFromFloat64(p) // Low is the smaller of two log-adjusted counts, the negative // of the exponent computed above. low := -exp // High is the greater of two log-adjusted counts (i.e., one // less than low, a smaller adjusted count means a larger // probability). high := low - 1 // Return these to probability values and use linear // interpolation to compute the required probability of // choosing the low-probability Sampler. lowP := expToFloat64(-low) highP := expToFloat64(-high) lowProb := (highP - p) / (highP - lowP) return uint8(low), uint8(high), lowProb // nolint: gosec // 8-bit sample. } open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/probability/consistent/base2_test.go000066400000000000000000000024071470323427300331750ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package consistent import ( "testing" "github.com/stretchr/testify/require" ) func TestSplitProb(t *testing.T) { require.Equal(t, -1, expFromFloat64(0.6)) //nolint:testifylint // false positive on expected-actual require.Equal(t, -2, expFromFloat64(0.4)) //nolint:testifylint // false positive on expected-actual require.Equal(t, 0.5, expToFloat64(-1)) require.Equal(t, 0.25, expToFloat64(-2)) for _, tc := range []struct { in float64 low uint8 lowProb float64 }{ // Probability 0.75 corresponds with choosing S=1 (the // "low" probability) 50% of the time and S=0 (the // "high" probability) 50% of the time. {0.75, 1, 0.5}, {0.6, 1, 0.8}, {0.9, 1, 0.2}, // Powers of 2 exactly {1, 0, 1}, {0.5, 1, 1}, {0.25, 2, 1}, // Smaller numbers {0.05, 5, 0.4}, {0.1, 4, 0.4}, // 0.1 == 0.4 * 1/16 + 0.6 * 1/8 {0.003, 9, 0.464}, // Special cases: {0, 63, 1}, } { low, high, lowProb := splitProb(tc.in) require.Equal(t, tc.low, low, "got %v want %v", low, tc.low) if lowProb != 1 { require.Equal(t, tc.low-1, high, "got %v want %v", high, tc.low-1) } require.InEpsilon(t, tc.lowProb, lowProb, 1e-6, "got %v want %v", lowProb, tc.lowProb) } } open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/probability/consistent/go.mod000066400000000000000000000011361470323427300317170ustar00rootroot00000000000000module go.opentelemetry.io/contrib/samplers/probability/consistent go 1.22 require ( github.com/stretchr/testify v1.9.0 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/sdk v1.31.0 go.opentelemetry.io/otel/trace v1.31.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect golang.org/x/sys v0.26.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/probability/consistent/go.sum000066400000000000000000000046721470323427300317540ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/probability/consistent/parent.go000066400000000000000000000040501470323427300324270ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package consistent // import "go.opentelemetry.io/contrib/samplers/probability/consistent" import ( "strings" "go.opentelemetry.io/otel" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" ) type ( parentProbabilitySampler struct { delegate sdktrace.Sampler } ) // ParentProbabilityBased is an implementation of the OpenTelemetry // Trace Sampler interface that provides additional checks for tracestate // Probability Sampling fields. func ParentProbabilityBased(root sdktrace.Sampler, samplers ...sdktrace.ParentBasedSamplerOption) sdktrace.Sampler { return &parentProbabilitySampler{ delegate: sdktrace.ParentBased(root, samplers...), } } // ShouldSample implements "go.opentelemetry.io/otel/sdk/trace".Sampler. func (p *parentProbabilitySampler) ShouldSample(params sdktrace.SamplingParameters) sdktrace.SamplingResult { psc := trace.SpanContextFromContext(params.ParentContext) // Note: We do not check psc.IsValid(), i.e., we repair the tracestate // with or without a parent TraceId and SpanId. state := psc.TraceState() otts, err := parseOTelTraceState(state.Get(traceStateKey), psc.IsSampled()) if err != nil { otel.Handle(err) value := otts.serialize() if len(value) > 0 { // Note: see the note in // "go.opentelemetry.io/otel/trace".TraceState.Insert(). The // error below is not a condition we're supposed to handle. state, _ = state.Insert(traceStateKey, value) } else { state = state.Delete(traceStateKey) } // Fix the broken tracestate before calling the delegate. params.ParentContext = trace.ContextWithSpanContext(params.ParentContext, psc.WithTraceState(state)) } return p.delegate.ShouldSample(params) } // Description returns the same description as the built-in // ParentBased sampler, with "ParentBased" replaced by // "ParentProbabilityBased". func (p *parentProbabilitySampler) Description() string { return "ParentProbabilityBased" + strings.TrimPrefix(p.delegate.Description(), "ParentBased") } open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/probability/consistent/parent_test.go000066400000000000000000000105761470323427300335000ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package consistent import ( "context" "strings" "testing" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" ) func TestParentSamplerDescription(t *testing.T) { opts := []sdktrace.ParentBasedSamplerOption{ sdktrace.WithRemoteParentNotSampled(sdktrace.AlwaysSample()), } root := ProbabilityBased(1) compare := sdktrace.ParentBased(root, opts...) parent := ParentProbabilityBased(root, opts...) require.Equal(t, strings.Replace( compare.Description(), "ParentBased", "ParentProbabilityBased", 1, ), parent.Description(), ) } func TestParentSamplerValidContext(t *testing.T) { parent := ParentProbabilityBased(sdktrace.NeverSample()) type testCase struct { in string sampled bool } for _, valid := range []testCase{ // sampled tests {"r:10", true}, {"r:10;a:b", true}, {"r:10;p:1", true}, {"r:10;p:10", true}, {"r:10;p:10;a:b", true}, {"r:10;p:63", true}, {"r:10;p:63;a:b", true}, {"p:0", true}, {"p:10;a:b", true}, {"p:63", true}, {"p:63;a:b", true}, // unsampled tests {"r:10", false}, {"r:10;a:b", false}, } { t.Run(testName(valid.in), func(t *testing.T) { traceID, _ := trace.TraceIDFromHex("4bf92f3577b34da6a3ce929d0e0e4736") spanID, _ := trace.SpanIDFromHex("00f067aa0ba902b7") traceState, err := trace.TraceState{}.Insert(traceStateKey, valid.in) require.NoError(t, err) sccfg := trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceState: traceState, } if valid.sampled { sccfg.TraceFlags = trace.FlagsSampled } parentCtx := trace.ContextWithSpanContext( context.Background(), trace.NewSpanContext(sccfg), ) result := parent.ShouldSample( sdktrace.SamplingParameters{ ParentContext: parentCtx, TraceID: traceID, Name: "test", Kind: trace.SpanKindServer, }, ) if valid.sampled { require.Equal(t, sdktrace.RecordAndSample, result.Decision) } else { require.Equal(t, sdktrace.Drop, result.Decision) } require.Equal(t, []attribute.KeyValue(nil), result.Attributes) require.Equal(t, valid.in, result.Tracestate.Get(traceStateKey)) }) } } func TestParentSamplerInvalidContext(t *testing.T) { parent := ParentProbabilityBased(sdktrace.NeverSample()) type testCase struct { in string sampled bool expect string } for _, invalid := range []testCase{ // sampled {"r:100", true, ""}, {"r:100;p:1", true, ""}, {"r:100;p:1;a:b", true, "a:b"}, {"r:10;p:100", true, "r:10"}, {"r:10;p:100;a:b", true, "r:10;a:b"}, // unsampled {"r:63;p:1", false, ""}, {"r:10;p:1", false, "r:10"}, {"r:10;p:1;a:b", false, "r:10;a:b"}, } { testInvalid := func(t *testing.T, isChildContext bool) { traceID, _ := trace.TraceIDFromHex("4bf92f3577b34da6a3ce929d0e0e4736") traceState, err := trace.TraceState{}.Insert(traceStateKey, invalid.in) require.NoError(t, err) sccfg := trace.SpanContextConfig{ TraceState: traceState, } if isChildContext { spanID, _ := trace.SpanIDFromHex("00f067aa0ba902b7") sccfg.TraceID = traceID sccfg.SpanID = spanID // Note: the other branch is testing a fabricated // situation where the context has a tracestate and // no TraceID. } if invalid.sampled { sccfg.TraceFlags = trace.FlagsSampled } parentCtx := trace.ContextWithSpanContext( context.Background(), trace.NewSpanContext(sccfg), ) result := parent.ShouldSample( sdktrace.SamplingParameters{ ParentContext: parentCtx, TraceID: sccfg.TraceID, Name: "test", Kind: trace.SpanKindServer, }, ) if isChildContext && invalid.sampled { require.Equal(t, sdktrace.RecordAndSample, result.Decision) } else { // if we're not a child context, ShouldSample // falls through to the delegate, which is NeverSample. require.Equal(t, sdktrace.Drop, result.Decision) } require.Equal(t, []attribute.KeyValue(nil), result.Attributes) require.Equal(t, invalid.expect, result.Tracestate.Get(traceStateKey)) } t.Run(testName(invalid.in)+"_with_parent", func(t *testing.T) { testInvalid(t, true) }) t.Run(testName(invalid.in)+"_no_parent", func(t *testing.T) { testInvalid(t, false) }) } } open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/probability/consistent/sampler.go000066400000000000000000000115421470323427300326050ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package consistent provides a consistent probability based sampler. package consistent // import "go.opentelemetry.io/contrib/samplers/probability/consistent" import ( "fmt" "math/bits" "math/rand" "sync" "go.opentelemetry.io/otel" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" ) type ( // ProbabilityBasedOption is an option to the // ConssitentProbabilityBased sampler. ProbabilityBasedOption interface { apply(*consistentProbabilityBasedConfig) } consistentProbabilityBasedConfig struct { source rand.Source } consistentProbabilityBasedRandomSource struct { rand.Source } consistentProbabilityBased struct { // "LAC" is an abbreviation for the logarithm of // adjusted count. Greater values have greater // representivity, therefore lesser sampling // probability. // lowLAC is the lower-probability log-adjusted count lowLAC uint8 // highLAC is the higher-probability log-adjusted // count. except for the zero probability special // case, highLAC == lowLAC - 1. highLAC uint8 // lowProb is the probability that lowLAC should be used, // in the interval (0, 1]. For exact powers of two and the // special case of 0 probability, lowProb == 1. lowProb float64 // lock protects rnd lock sync.Mutex rnd *rand.Rand } ) // WithRandomSource sets the source of the randomness used by the Sampler. func WithRandomSource(source rand.Source) ProbabilityBasedOption { return consistentProbabilityBasedRandomSource{source} } func (s consistentProbabilityBasedRandomSource) apply(cfg *consistentProbabilityBasedConfig) { cfg.source = s.Source } // ProbabilityBased samples a given fraction of traces. Based on the // OpenTelemetry specification, this Sampler supports only power-of-two // fractions. When the input fraction is not a power of two, it will // be rounded down. // - Fractions >= 1 will always sample. // - Fractions < 2^-62 are treated as zero. // // This Sampler sets the OpenTelemetry tracestate p-value and/or r-value. // // To respect the parent trace's `SampledFlag`, this sampler should be // used as the root delegate of a `Parent` sampler. func ProbabilityBased(fraction float64, opts ...ProbabilityBasedOption) sdktrace.Sampler { cfg := consistentProbabilityBasedConfig{ source: rand.NewSource(rand.Int63()), //nolint:gosec // G404: Use of weak random number generator (math/rand instead of crypto/rand) is ignored as this is not security-sensitive. } for _, opt := range opts { opt.apply(&cfg) } if fraction < 0 { fraction = 0 } else if fraction > 1 { fraction = 1 } lowLAC, highLAC, lowProb := splitProb(fraction) return &consistentProbabilityBased{ lowLAC: lowLAC, highLAC: highLAC, lowProb: lowProb, rnd: rand.New(cfg.source), //nolint:gosec // G404: Use of weak random number generator (math/rand instead of crypto/rand) is ignored as this is not security-sensitive. } } func (cs *consistentProbabilityBased) newR() uint8 { cs.lock.Lock() defer cs.lock.Unlock() return uint8(bits.LeadingZeros64(uint64(cs.rnd.Int63())) - 1) // nolint: gosec // 8-bit sample. } func (cs *consistentProbabilityBased) lowChoice() bool { cs.lock.Lock() defer cs.lock.Unlock() return cs.rnd.Float64() < cs.lowProb } // ShouldSample implements "go.opentelemetry.io/otel/sdk/trace".Sampler. func (cs *consistentProbabilityBased) ShouldSample(p sdktrace.SamplingParameters) sdktrace.SamplingResult { psc := trace.SpanContextFromContext(p.ParentContext) // Note: this ignores whether psc.IsValid() because this // allows other otel trace state keys to pass through even // for root decisions. state := psc.TraceState() otts, err := parseOTelTraceState(state.Get(traceStateKey), psc.IsSampled()) if err != nil { // Note: a state.Insert(traceStateKey) // follows, nothing else needs to be done here. otel.Handle(err) } if !otts.hasRValue() { otts.rvalue = cs.newR() } var decision sdktrace.SamplingDecision var lac uint8 if cs.lowProb == 1 || cs.lowChoice() { lac = cs.lowLAC } else { lac = cs.highLAC } if lac <= otts.rvalue { decision = sdktrace.RecordAndSample otts.pvalue = lac } else { decision = sdktrace.Drop otts.pvalue = invalidValue } // Note: see the note in // "go.opentelemetry.io/otel/trace".TraceState.Insert(). The // error below is not a condition we're supposed to handle. state, _ = state.Insert(traceStateKey, otts.serialize()) return sdktrace.SamplingResult{ Decision: decision, Tracestate: state, } } // Description returns "ProbabilityBased{%g}" with the configured probability. func (cs *consistentProbabilityBased) Description() string { var prob float64 if cs.lowLAC != pZeroValue { prob = cs.lowProb * expToFloat64(-int(cs.lowLAC)) prob += (1 - cs.lowProb) * expToFloat64(-int(cs.highLAC)) } return fmt.Sprintf("ProbabilityBased{%g}", prob) } open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/probability/consistent/sampler_test.go000066400000000000000000000130021470323427300336350ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package consistent import ( "context" "fmt" "math/rand" "strings" "sync" "testing" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" ) type ( testDegrees int pValue int testErrorHandler struct { lock sync.Mutex errors []error } ) func parsePR(s string) (p, r string) { for _, kvf := range strings.Split(s, ";") { kv := strings.SplitN(kvf, ":", 2) switch kv[0] { case "p": p = kv[1] case "r": r = kv[1] } } return } func (eh *testErrorHandler) Handle(err error) { eh.lock.Lock() defer eh.lock.Unlock() eh.errors = append(eh.errors, err) } func (eh *testErrorHandler) Errors() []error { eh.lock.Lock() defer eh.lock.Unlock() return eh.errors } func TestSamplerDescription(t *testing.T) { const minProb = 0x1p-62 // 2.168404344971009e-19 for _, tc := range []struct { prob float64 expect string }{ {1, "ProbabilityBased{1}"}, {0, "ProbabilityBased{0}"}, {0.75, "ProbabilityBased{0.75}"}, {0.05, "ProbabilityBased{0.05}"}, {0.003, "ProbabilityBased{0.003}"}, {0.99999999, "ProbabilityBased{0.99999999}"}, {0.00000001, "ProbabilityBased{1e-08}"}, {minProb, "ProbabilityBased{2.168404344971009e-19}"}, {minProb * 1.5, "ProbabilityBased{3.2526065174565133e-19}"}, {3e-19, "ProbabilityBased{3e-19}"}, // out-of-range > 1 {1.01, "ProbabilityBased{1}"}, {101.1, "ProbabilityBased{1}"}, // out-of-range < 2^-62 {-1, "ProbabilityBased{0}"}, {-0.001, "ProbabilityBased{0}"}, {minProb * 0.999, "ProbabilityBased{0}"}, } { s := ProbabilityBased(tc.prob) require.Equal(t, tc.expect, s.Description(), "%#v", tc.prob) } } func getUnknowns(otts otelTraceState) string { otts.pvalue = invalidValue otts.rvalue = invalidValue return otts.serialize() } func TestSamplerBehavior(t *testing.T) { type testGroup struct { probability float64 minP uint8 maxP uint8 } type testCase struct { isRoot bool parentSampled bool ctxTracestate string hasErrors bool } for _, group := range []testGroup{ {1.0, 0, 0}, {0.75, 0, 1}, {0.5, 1, 1}, {0, 63, 63}, } { t.Run(fmt.Sprint(group.probability), func(t *testing.T) { for _, test := range []testCase{ // roots do not care if the context is // sampled, however preserve other // otel tracestate keys {true, false, "", false}, {true, false, "a:b", false}, // non-roots insert r {false, true, "", false}, {false, true, "a:b", false}, {false, false, "", false}, {false, false, "a:b", false}, // error cases: r-p inconsistency {false, true, "r:10;p:20", true}, {false, true, "r:10;p:20;a:b", true}, {false, false, "r:10;p:5", true}, {false, false, "r:10;p:5;a:b", true}, // error cases: out-of-range {false, false, "r:100", true}, {false, false, "r:100;a:b", true}, {false, true, "r:100;p:100", true}, {false, true, "r:100;p:100;a:b", true}, {false, true, "r:10;p:100", true}, {false, true, "r:10;p:100;a:b", true}, } { t.Run(testName(test.ctxTracestate), func(t *testing.T) { handler := &testErrorHandler{} otel.SetErrorHandler(handler) src := rand.NewSource(99999199999) sampler := ProbabilityBased(group.probability, WithRandomSource(src)) traceID, _ := trace.TraceIDFromHex("4bf92f3577b34da6a3ce929d0e0e4736") spanID, _ := trace.SpanIDFromHex("00f067aa0ba902b7") traceState := trace.TraceState{} if test.ctxTracestate != "" { var err error traceState, err = traceState.Insert(traceStateKey, test.ctxTracestate) require.NoError(t, err) } sccfg := trace.SpanContextConfig{ TraceState: traceState, } if !test.isRoot { sccfg.TraceID = traceID sccfg.SpanID = spanID } if test.parentSampled { sccfg.TraceFlags = trace.FlagsSampled } parentCtx := trace.ContextWithSpanContext( context.Background(), trace.NewSpanContext(sccfg), ) // Note: the error below is sometimes expected testState, _ := parseOTelTraceState(test.ctxTracestate, test.parentSampled) hasRValue := testState.hasRValue() const repeats = 10 for i := 0; i < repeats; i++ { result := sampler.ShouldSample( sdktrace.SamplingParameters{ ParentContext: parentCtx, TraceID: traceID, Name: "test", Kind: trace.SpanKindServer, }, ) sampled := result.Decision == sdktrace.RecordAndSample // The result is deterministically random. Parse the tracestate // to see that it is consistent. otts, err := parseOTelTraceState(result.Tracestate.Get(traceStateKey), sampled) require.NoError(t, err) require.True(t, otts.hasRValue()) require.Equal(t, []attribute.KeyValue(nil), result.Attributes) if otts.hasPValue() { require.LessOrEqual(t, group.minP, otts.pvalue) require.LessOrEqual(t, otts.pvalue, group.maxP) require.Equal(t, sdktrace.RecordAndSample, result.Decision) } else { require.Equal(t, sdktrace.Drop, result.Decision) } require.Equal(t, getUnknowns(testState), getUnknowns(otts)) if hasRValue { require.Equal(t, testState.rvalue, otts.rvalue) } if test.hasErrors { require.NotEmpty(t, handler.Errors()) } else { require.Empty(t, handler.Errors()) } } }) } }) } } open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/probability/consistent/statistical_test.go000066400000000000000000000206111470323427300345220ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 //go:build !race // +build !race package consistent import ( "context" "fmt" "math" "math/rand" "strconv" "testing" "time" "github.com/stretchr/testify/require" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/sdk/trace/tracetest" ) const ( oneDegree testDegrees = 1 twoDegrees testDegrees = 2 ) var ( trials = 20 populationSize = 1e5 // These may be computed using Gonum, e.g., // import "gonum.org/v1/gonum/stat/distuv" // with significance = 1 / float64(trials) = 0.05 // chiSquaredDF1 = distuv.ChiSquared{K: 1}.Quantile(significance) // chiSquaredDF2 = distuv.ChiSquared{K: 2}.Quantile(significance) // // These have been specified using significance = 0.05: chiSquaredDF1 = 0.003932140000019522 chiSquaredDF2 = 0.1025865887751011 chiSquaredByDF = [3]float64{ 0, chiSquaredDF1, chiSquaredDF2, } ) func TestSamplerStatistics(t *testing.T) { seedBankRng := rand.New(rand.NewSource(77777677777)) seedBank := make([]int64, 7) // N.B. Max=6 below. for i := range seedBank { seedBank[i] = seedBankRng.Int63() } type ( testCase struct { // prob is the sampling probability under test. prob float64 // upperP reflects the larger of the one or two // distinct adjusted counts represented in the test. // // For power-of-two tests, there is one distinct p-value, // and each span counts as 2**upperP representative spans. // // For non-power-of-two tests, there are two distinct // p-values expected, the test is specified using the // larger of these values corresponding with the // smaller sampling probability. The sampling // probability under test rounded down to the nearest // power of two is expected to equal 2**(-upperP). upperP pValue // degrees is 1 for power-of-two tests and 2 for // non-power-of-two tests. degrees testDegrees // seedIndex is the index into seedBank of the test seed. // If this is -1 the code below will search for the smallest // seed index that passes the test. seedIndex int } testResult struct { test testCase expected []float64 } ) var ( testSummary []testResult allTests = []testCase{ // Non-powers of two {0.90000, 1, twoDegrees, 3}, {0.60000, 1, twoDegrees, 2}, {0.33000, 2, twoDegrees, 2}, {0.13000, 3, twoDegrees, 1}, {0.10000, 4, twoDegrees, 0}, {0.05000, 5, twoDegrees, 0}, {0.01700, 6, twoDegrees, 2}, {0.01000, 7, twoDegrees, 2}, {0.00500, 8, twoDegrees, 2}, {0.00290, 9, twoDegrees, 4}, {0.00100, 10, twoDegrees, 6}, {0.00050, 11, twoDegrees, 0}, // Powers of two {0x1p-1, 1, oneDegree, 0}, {0x1p-4, 4, oneDegree, 0}, {0x1p-7, 7, oneDegree, 1}, } ) // Limit the test runtime by choosing 3 of the above // non-deterministically rand.New(rand.NewSource(time.Now().UnixNano())).Shuffle(len(allTests), func(i, j int) { allTests[i], allTests[j] = allTests[j], allTests[i] }) allTests = allTests[0:3] for _, test := range allTests { t.Run(fmt.Sprint(test.prob), func(t *testing.T) { var expected []float64 trySeedIndex := 0 for { var seed int64 seedIndex := test.seedIndex if seedIndex >= 0 { seed = seedBank[seedIndex] } else { seedIndex = trySeedIndex seed = seedBank[trySeedIndex] trySeedIndex++ } countFailures := func(src rand.Source) int { failed := 0 for j := 0; j < trials; j++ { var x float64 x, expected = sampleTrials(t, test.prob, test.degrees, test.upperP, src) if x < chiSquaredByDF[test.degrees] { failed++ } } return failed } failed := countFailures(rand.NewSource(seed)) if failed != 1 && test.seedIndex < 0 { t.Logf("%d probabilistic failures, trying a new seed for %g was 0x%x", failed, test.prob, seed) continue } else if failed != 1 { t.Errorf("wrong number of probabilistic failures for %g, should be 1 was %d for seed 0x%x", test.prob, failed, seed) } else if test.seedIndex < 0 { t.Logf("update the test for %g to use seed index %d", test.prob, seedIndex) t.Fail() return } else { // Note: this can be uncommented to verify that the preceding seed failed the test, // however this just doubles runtime and adds little evidence. For example: // if seedIndex != 0 && countFailures(rand.NewSource(seedBank[seedIndex-1])) == 1 { // t.Logf("update the test for %g to use seed index < %d", test.prob, seedIndex) // t.Fail() // } break } } testSummary = append(testSummary, testResult{ test: test, expected: expected, }) }) } // Note: This produces a table that should match what is in // the specification if it's the same test. for idx, res := range testSummary { var probability, pvalues, expectLower, expectUpper, expectUnsampled string if res.test.degrees == twoDegrees { probability = fmt.Sprintf("%.6f", res.test.prob) pvalues = fmt.Sprint(res.test.upperP-1, ", ", res.test.upperP) expectUnsampled = fmt.Sprintf("%.10g", res.expected[0]) expectLower = fmt.Sprintf("%.10g", res.expected[1]) expectUpper = fmt.Sprintf("%.10g", res.expected[2]) } else { probability = fmt.Sprintf("%x (%.6f)", res.test.prob, res.test.prob) pvalues = fmt.Sprint(res.test.upperP) expectUnsampled = fmt.Sprintf("%.10g", res.expected[0]) expectLower = fmt.Sprintf("%.10g", res.expected[1]) expectUpper = "n/a" } t.Logf("| %d | %s | %s | %s | %s | %s |\n", idx+1, probability, pvalues, expectLower, expectUpper, expectUnsampled) } } func sampleTrials(t *testing.T, prob float64, degrees testDegrees, upperP pValue, source rand.Source) (float64, []float64) { ctx := context.Background() sampler := ProbabilityBased( prob, WithRandomSource(source), ) recorder := &tracetest.InMemoryExporter{} provider := sdktrace.NewTracerProvider( sdktrace.WithSyncer(recorder), sdktrace.WithSampler(sampler), ) tracer := provider.Tracer("test") for i := 0; i < int(populationSize); i++ { _, span := tracer.Start(ctx, "span") span.End() } var minP, maxP pValue counts := map[pValue]int64{} for idx, r := range recorder.GetSpans() { ts := r.SpanContext.TraceState() p, _ := parsePR(ts.Get("ot")) pi, err := strconv.ParseUint(p, 10, 64) require.NoError(t, err) if idx == 0 { maxP = pValue(pi) minP = maxP } else { if pValue(pi) < minP { minP = pValue(pi) } if pValue(pi) > maxP { maxP = pValue(pi) } } counts[pValue(pi)]++ } require.Less(t, maxP, minP+pValue(degrees), "%v %v %v", minP, maxP, degrees) require.Less(t, maxP, pValue(63)) require.LessOrEqual(t, len(counts), 2) var ceilingProb, floorProb, floorChoice float64 // Note: we have to test len(counts) == 0 because this outcome // is actually possible, just very unlikely. If this happens // during development, a new initial seed must be used for // this test. // // The test specification ensures the test ensures there are // at least 20 expected items per category in these tests. require.NotEmpty(t, counts) if degrees == 2 { // Note: because the test is probabilistic, we can't be // sure that both the min and max P values happen. We // can only assert that one of these is true. require.GreaterOrEqual(t, maxP, upperP-1) require.GreaterOrEqual(t, minP, upperP-1) require.LessOrEqual(t, maxP, upperP) require.LessOrEqual(t, minP, upperP) require.LessOrEqual(t, maxP-minP, 1) ceilingProb = 1 / float64(int64(1)<<(upperP-1)) floorProb = 1 / float64(int64(1)< !hasRValue() pvalue: invalidValue, // out-of-range => !hasPValue() } } func (otts otelTraceState) serialize() string { var sb strings.Builder semi := func() { if sb.Len() != 0 { _, _ = sb.WriteString(";") } } if otts.hasPValue() { _, _ = sb.WriteString(fmt.Sprintf("p:%d", otts.pvalue)) } if otts.hasRValue() { semi() _, _ = sb.WriteString(fmt.Sprintf("r:%d", otts.rvalue)) } for _, unk := range otts.unknown { ex := 0 if sb.Len() != 0 { ex = 1 } if sb.Len()+ex+len(unk) > traceStateSizeLimit { // Note: should this generate an explicit error? break } semi() _, _ = sb.WriteString(unk) } return sb.String() } func isValueByte(r byte) bool { if isLCAlphaNum(r) { return true } if isUCAlpha(r) { return true } return r == '.' || r == '_' || r == '-' } func isLCAlphaNum(r byte) bool { if isLCAlpha(r) { return true } return r >= '0' && r <= '9' } func isLCAlpha(r byte) bool { return r >= 'a' && r <= 'z' } func isUCAlpha(r byte) bool { return r >= 'A' && r <= 'Z' } func parseOTelTraceState(ts string, isSampled bool) (otelTraceState, error) { // nolint: revive var pval, rval string var unknown []string if len(ts) == 0 { return newTraceState(), nil } if len(ts) > traceStateSizeLimit { return newTraceState(), errTraceStateSyntax } for len(ts) > 0 { eqPos := 0 for ; eqPos < len(ts); eqPos++ { if eqPos == 0 { if isLCAlpha(ts[eqPos]) { continue } } else if isLCAlphaNum(ts[eqPos]) { continue } break } if eqPos == 0 || eqPos == len(ts) || ts[eqPos] != ':' { return newTraceState(), errTraceStateSyntax } key := ts[0:eqPos] tail := ts[eqPos+1:] sepPos := 0 for ; sepPos < len(tail); sepPos++ { if isValueByte(tail[sepPos]) { continue } break } if key == pValueSubkey { // Note: does the spec say how to handle duplicates? pval = tail[0:sepPos] } else if key == rValueSubkey { rval = tail[0:sepPos] } else { unknown = append(unknown, ts[0:sepPos+eqPos+1]) } if sepPos < len(tail) && tail[sepPos] != ';' { return newTraceState(), errTraceStateSyntax } if sepPos == len(tail) { break } ts = tail[sepPos+1:] // test for a trailing ; if ts == "" { return newTraceState(), errTraceStateSyntax } } otts := newTraceState() otts.unknown = unknown // Note: set R before P, so that P won't propagate if R has an error. value, err := parseNumber(rValueSubkey, rval, pZeroValue-1) if err != nil { return otts, err } otts.rvalue = value value, err = parseNumber(pValueSubkey, pval, pZeroValue) if err != nil { return otts, err } otts.pvalue = value // Invariant checking: unset P when the values are inconsistent. if otts.hasPValue() && otts.hasRValue() { implied := otts.pvalue <= otts.rvalue || otts.pvalue == pZeroValue if !isSampled || !implied { // Note: the error ensures the parent-based // sampler repairs the broken tracestate entry. otts.pvalue = invalidValue return otts, parseError(pValueSubkey, errTraceStateInconsistent) } } return otts, nil } func parseNumber(key string, input string, maximum uint8) (uint8, error) { if input == "" { return maximum + 1, nil } value, err := strconv.ParseUint(input, 10, 64) if err != nil { return maximum + 1, parseError(key, err) } if value > uint64(maximum) { return maximum + 1, parseError(key, strconv.ErrRange) } // `value` is strictly less then the uint8 maximum. This cast is safe. return uint8(value), nil // nolint: gosec } func parseError(key string, err error) error { return fmt.Errorf("otel tracestate: %s-value %w", key, err) } func (otts otelTraceState) hasRValue() bool { return otts.rvalue < pZeroValue } func (otts otelTraceState) hasPValue() bool { return otts.pvalue <= pZeroValue } open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/probability/consistent/tracestate_test.go000066400000000000000000000206721470323427300343440ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package consistent import ( "strconv" "strings" "testing" "github.com/stretchr/testify/require" ) func testName(in string) string { x := strings.NewReplacer(":", "_", ";", "_").Replace(in) if len(x) > 32 { return "" } return x } func TestNewTraceState(t *testing.T) { otts := newTraceState() require.False(t, otts.hasPValue()) require.False(t, otts.hasRValue()) require.Equal(t, "", otts.serialize()) } func TestTraceStatePRValueSerialize(t *testing.T) { otts := newTraceState() otts.pvalue = 3 otts.rvalue = 4 otts.unknown = []string{"a:b", "c:d"} require.True(t, otts.hasPValue()) require.True(t, otts.hasRValue()) require.Equal(t, "p:3;r:4;a:b;c:d", otts.serialize()) } func TestTraceStateSerializeOverflow(t *testing.T) { long := "x:" + strings.Repeat(".", 254) otts := newTraceState() otts.unknown = []string{long} // this drops the extra key, sorry! require.Equal(t, long, otts.serialize()) otts.pvalue = 1 require.Equal(t, "p:1", otts.serialize()) } func TestParseTraceStateUnsampled(t *testing.T) { type testCase struct { in string rval uint8 expectErr error } const notset = 255 for _, test := range []testCase{ // All are unsampled tests, i.e., `sampled` is not set in traceparent. {"r:2", 2, nil}, {"r:1;", notset, strconv.ErrSyntax}, {"r:1", 1, nil}, {"r:1=p:2", notset, strconv.ErrSyntax}, {"r:1;p:2=s:3", notset, strconv.ErrSyntax}, {":1;p:2=s:3", notset, strconv.ErrSyntax}, {":;p:2=s:3", notset, strconv.ErrSyntax}, {":;:", notset, strconv.ErrSyntax}, {":", notset, strconv.ErrSyntax}, {"", notset, nil}, {"r:;p=1", notset, strconv.ErrSyntax}, {"r:1", 1, nil}, {"r:10", 10, nil}, {"r:33", 33, nil}, {"r:61", 61, nil}, {"r:62", 62, nil}, // max r-value {"r:63", notset, strconv.ErrRange}, // out-of-range {"r:100", notset, strconv.ErrRange}, // out-of-range {"r:100001", notset, strconv.ErrRange}, // out-of-range {"p:64", notset, strconv.ErrRange}, {"p:100", notset, strconv.ErrRange}, {"r:1a", notset, strconv.ErrSyntax}, // not-hexadecimal {"p:-1", notset, strconv.ErrSyntax}, // non-negative // Inconsistent trace state: any p-value when unsampled {"p:4;r:2", 2, errTraceStateInconsistent}, {"p:1;r:2", 2, errTraceStateInconsistent}, } { t.Run(testName(test.in), func(t *testing.T) { // Note: passing isSampled=false as stated above. otts, err := parseOTelTraceState(test.in, false) require.False(t, otts.hasPValue(), "should have no p-value") if test.expectErr != nil { require.ErrorIs(t, err, test.expectErr, "not expecting %v", err) } if test.rval != notset { require.True(t, otts.hasRValue()) require.Equal(t, test.rval, otts.rvalue) } else { require.False(t, otts.hasRValue(), "should have no r-value") } require.EqualValues(t, []string(nil), otts.unknown) if test.expectErr == nil { // Require serialize to round-trip otts2, err := parseOTelTraceState(otts.serialize(), false) require.NoError(t, err) require.Equal(t, otts, otts2) } }) } } func TestParseTraceStateSampled(t *testing.T) { type testCase struct { in string rval, pval uint8 expectErr error } const notset = 255 for _, test := range []testCase{ // All are sampled tests, i.e., `sampled` is set in traceparent. {"r:2;p:2", 2, 2, nil}, {"r:2;p:1", 2, 1, nil}, {"r:2;p:0", 2, 0, nil}, {"r:1;p:1", 1, 1, nil}, {"r:1;p:0", 1, 0, nil}, {"r:0;p:0", 0, 0, nil}, {"r:62;p:0", 62, 0, nil}, {"r:62;p:62", 62, 62, nil}, // The important special case: {"r:0;p:63", 0, 63, nil}, {"r:2;p:63", 2, 63, nil}, {"r:62;p:63", 62, 63, nil}, // Inconsistent p causes unset p-value. {"r:2;p:3", 2, notset, errTraceStateInconsistent}, {"r:2;p:4", 2, notset, errTraceStateInconsistent}, {"r:2;p:62", 2, notset, errTraceStateInconsistent}, {"r:0;p:1", 0, notset, errTraceStateInconsistent}, {"r:1;p:2", 1, notset, errTraceStateInconsistent}, {"r:61;p:62", 61, notset, errTraceStateInconsistent}, // Inconsistent r causes unset p-value and r-value. {"r:63;p:2", notset, notset, strconv.ErrRange}, {"r:120;p:2", notset, notset, strconv.ErrRange}, {"r:ab;p:2", notset, notset, strconv.ErrSyntax}, // Syntax is tested before range errors {"r:ab;p:77", notset, notset, strconv.ErrSyntax}, // p without r (when sampled) {"p:1", notset, 1, nil}, {"p:62", notset, 62, nil}, {"p:63", notset, 63, nil}, // r without p (when sampled) {"r:2", 2, notset, nil}, {"r:62", 62, notset, nil}, {"r:0", 0, notset, nil}, } { t.Run(testName(test.in), func(t *testing.T) { // Note: passing isSampled=true as stated above. otts, err := parseOTelTraceState(test.in, true) if test.expectErr != nil { require.ErrorIs(t, err, test.expectErr, "not expecting %v", err) } else { require.NoError(t, err) } if test.pval != notset { require.True(t, otts.hasPValue()) require.Equal(t, test.pval, otts.pvalue) } else { require.False(t, otts.hasPValue(), "should have no p-value") } if test.rval != notset { require.True(t, otts.hasRValue()) require.Equal(t, test.rval, otts.rvalue) } else { require.False(t, otts.hasRValue(), "should have no r-value") } require.EqualValues(t, []string(nil), otts.unknown) if test.expectErr == nil { // Require serialize to round-trip otts2, err := parseOTelTraceState(otts.serialize(), true) require.NoError(t, err) require.Equal(t, otts, otts2) } }) } } func TestParseTraceStateExtra(t *testing.T) { type testCase struct { in string rval, pval uint8 sampled bool extra []string expectErr error } const notset = 255 for _, test := range []testCase{ // one field {"e100:1", notset, notset, false, []string{"e100:1"}, nil}, // two fields {"e1:1;e2:2", notset, notset, false, []string{"e1:1", "e2:2"}, nil}, {"e1:1;e2:2", notset, notset, false, []string{"e1:1", "e2:2"}, nil}, // one extra key, three ways {"r:2;p:2;extra:stuff", 2, 2, true, []string{"extra:stuff"}, nil}, {"extra:stuff;r:2;p:2", 2, 2, true, []string{"extra:stuff"}, nil}, {"p:2;extra:stuff;r:2", 2, 2, true, []string{"extra:stuff"}, nil}, // extra with inconsistent p with and without sampling {"r:3;extra:stuff;p:4", 3, notset, true, []string{"extra:stuff"}, errTraceStateInconsistent}, {"extra:stuff;r:3;p:2", 3, notset, false, []string{"extra:stuff"}, errTraceStateInconsistent}, // two extra fields {"e100:100;r:2;p:1;e101:101", 2, 1, true, []string{"e100:100", "e101:101"}, nil}, {"r:2;p:1;e100:100;e101:101", 2, 1, true, []string{"e100:100", "e101:101"}, nil}, {"e100:100;e101:101;r:2;p:1", 2, 1, true, []string{"e100:100", "e101:101"}, nil}, // parse error prevents capturing unrecognized keys {"1:1;u:V", notset, notset, true, nil, strconv.ErrSyntax}, {"X:1;u:V", notset, notset, true, nil, strconv.ErrSyntax}, {"x:1;u:V", notset, notset, true, []string{"x:1", "u:V"}, nil}, // no trailing ; {"x:1;", notset, notset, true, nil, strconv.ErrSyntax}, // empty key {"x:", notset, notset, true, []string{"x:"}, nil}, // charset test {"x:0X1FFF;y:.-_-.;z:", notset, notset, true, []string{"x:0X1FFF", "y:.-_-.", "z:"}, nil}, {"x1y2z3:1-2-3;y1:y_1;xy:-;r:50", 50, notset, true, []string{"x1y2z3:1-2-3", "y1:y_1", "xy:-"}, nil}, // size exceeded {"x:" + strings.Repeat("_", 255), notset, notset, false, nil, strconv.ErrSyntax}, {"x:" + strings.Repeat("_", 254), notset, notset, false, []string{"x:" + strings.Repeat("_", 254)}, nil}, } { t.Run(testName(test.in), func(t *testing.T) { // Note: These tests are independent of sampling state, // so both are tested. otts, err := parseOTelTraceState(test.in, test.sampled) if test.expectErr != nil { require.ErrorIs(t, err, test.expectErr, "not expecting %v", err) } else { require.NoError(t, err) } if test.pval != notset { require.True(t, otts.hasPValue()) require.Equal(t, test.pval, otts.pvalue) } else { require.False(t, otts.hasPValue(), "should have no p-value") } if test.rval != notset { require.True(t, otts.hasRValue()) require.Equal(t, test.rval, otts.rvalue) } else { require.False(t, otts.hasRValue(), "should have no r-value") } require.EqualValues(t, test.extra, otts.unknown) // on success w/o r-value or p-value, serialize() should not modify if !otts.hasRValue() && !otts.hasPValue() && test.expectErr == nil { require.Equal(t, test.in, otts.serialize()) } }) } } open-telemetry-opentelemetry-go-contrib-e5abccb/samplers/probability/consistent/version.go000066400000000000000000000010311470323427300326170ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package consistent // import "go.opentelemetry.io/contrib/samplers/probability/consistent" // Version is the current release version of the consistent probability // sampler. func Version() string { return "0.25.0" // This string is updated by the pre_release.sh script during release } // SemVersion is the semantic version to be supplied to tracer/meter creation. // // Deprecated: Use [Version] instead. func SemVersion() string { return Version() } open-telemetry-opentelemetry-go-contrib-e5abccb/tools/000077500000000000000000000000001470323427300234115ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/tools/go.mod000066400000000000000000000260451470323427300245260ustar00rootroot00000000000000module go.opentelemetry.io/contrib/tools go 1.22.1 exclude github.com/blizzy78/varnamelen v0.6.1 require ( github.com/atombender/go-jsonschema v0.16.0 github.com/client9/misspell v0.3.4 github.com/golangci/golangci-lint v1.61.0 github.com/itchyny/gojq v0.12.16 github.com/jcchavezs/porto v0.6.0 github.com/wadey/gocovmerge v0.0.0-20160331181800-b5bfa59ec0ad go.opentelemetry.io/build-tools/crosslink v0.14.0 go.opentelemetry.io/build-tools/gotmpl v0.14.0 go.opentelemetry.io/build-tools/multimod v0.14.0 golang.org/x/exp v0.0.0-20241004190924-225e2abe05e6 golang.org/x/tools v0.26.0 golang.org/x/vuln v1.1.3 ) require ( 4d63.com/gocheckcompilerdirectives v1.2.1 // indirect 4d63.com/gochecknoglobals v0.2.1 // indirect dario.cat/mergo v1.0.0 // indirect github.com/4meepo/tagalign v1.3.4 // indirect github.com/Abirdcfly/dupword v0.1.1 // indirect github.com/Antonboom/errname v0.1.13 // indirect github.com/Antonboom/nilnil v0.1.9 // indirect github.com/Antonboom/testifylint v1.4.3 // indirect github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect github.com/Crocmagnon/fatcontext v0.5.2 // indirect github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0 // indirect github.com/Masterminds/semver/v3 v3.3.0 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect github.com/OpenPeeDeeP/depguard/v2 v2.2.0 // indirect github.com/ProtonMail/go-crypto v1.0.0 // indirect github.com/alecthomas/go-check-sumtype v0.1.4 // indirect github.com/alexkohler/nakedret/v2 v2.0.4 // indirect github.com/alexkohler/prealloc v1.0.0 // indirect github.com/alingse/asasalint v0.0.11 // indirect github.com/ashanbrown/forbidigo v1.6.0 // indirect github.com/ashanbrown/makezero v1.1.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bkielbasa/cyclop v1.2.1 // indirect github.com/blizzy78/varnamelen v0.8.0 // indirect github.com/bombsimon/wsl/v4 v4.4.1 // indirect github.com/breml/bidichk v0.2.7 // indirect github.com/breml/errchkjson v0.3.6 // indirect github.com/butuzov/ireturn v0.3.0 // indirect github.com/butuzov/mirror v1.2.0 // indirect github.com/catenacyber/perfsprint v0.7.1 // indirect github.com/ccojocar/zxcvbn-go v1.0.2 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/charithe/durationcheck v0.0.10 // indirect github.com/chavacava/garif v0.1.0 // indirect github.com/ckaznocha/intrange v0.2.0 // indirect github.com/cloudflare/circl v1.3.7 // indirect github.com/curioswitch/go-reassign v0.2.0 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/daixiang0/gci v0.13.5 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/denis-tingaikin/go-header v0.5.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/ettle/strcase v0.2.0 // indirect github.com/fatih/color v1.17.0 // indirect github.com/fatih/structtag v1.2.0 // indirect github.com/firefart/nonamedreturns v1.0.5 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fzipp/gocyclo v0.6.0 // indirect github.com/ghostiam/protogetter v0.3.6 // indirect github.com/go-critic/go-critic v0.11.4 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.5.0 // indirect github.com/go-git/go-git/v5 v5.12.0 // indirect github.com/go-toolsmith/astcast v1.1.0 // indirect github.com/go-toolsmith/astcopy v1.1.0 // indirect github.com/go-toolsmith/astequal v1.2.0 // indirect github.com/go-toolsmith/astfmt v1.1.0 // indirect github.com/go-toolsmith/astp v1.1.0 // indirect github.com/go-toolsmith/strparse v1.1.0 // indirect github.com/go-toolsmith/typep v1.1.0 // indirect github.com/go-viper/mapstructure/v2 v2.1.0 // indirect github.com/go-xmlfmt/xmlfmt v1.1.2 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/goccy/go-yaml v1.11.3 // indirect github.com/gofrs/flock v0.12.1 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a // indirect github.com/golangci/gofmt v0.0.0-20240816233607-d8596aa466a9 // indirect github.com/golangci/misspell v0.6.0 // indirect github.com/golangci/modinfo v0.3.4 // indirect github.com/golangci/plugin-module-register v0.1.1 // indirect github.com/golangci/revgrep v0.5.3 // indirect github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/gordonklaus/ineffassign v0.1.0 // indirect github.com/gostaticanalysis/analysisutil v0.7.1 // indirect github.com/gostaticanalysis/comment v1.4.2 // indirect github.com/gostaticanalysis/forcetypeassert v0.1.0 // indirect github.com/gostaticanalysis/nilerr v0.1.1 // indirect github.com/hashicorp/go-version v1.7.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hexops/gotextdiff v1.0.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/itchyny/timefmt-go v0.1.6 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jgautheron/goconst v1.7.1 // indirect github.com/jingyugao/rowserrcheck v1.1.1 // indirect github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af // indirect github.com/jjti/go-spancheck v0.6.2 // indirect github.com/julz/importas v0.1.0 // indirect github.com/karamaru-alpha/copyloopvar v1.1.0 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/kisielk/errcheck v1.7.0 // indirect github.com/kkHAIKE/contextcheck v1.1.5 // indirect github.com/kulti/thelper v0.6.3 // indirect github.com/kunwardeep/paralleltest v1.0.10 // indirect github.com/kyoh86/exportloopref v0.1.11 // indirect github.com/lasiar/canonicalheader v1.1.1 // indirect github.com/ldez/gomoddirectives v0.2.4 // indirect github.com/ldez/tagliatelle v0.5.0 // indirect github.com/leonklingele/grouper v1.1.2 // indirect github.com/lufeee/execinquery v1.2.1 // indirect github.com/macabu/inamedparam v0.1.3 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/maratori/testableexamples v1.0.0 // indirect github.com/maratori/testpackage v1.1.1 // indirect github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mgechev/revive v1.3.9 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/moricho/tparallel v0.3.2 // indirect github.com/nakabonne/nestif v0.3.1 // indirect github.com/nishanths/exhaustive v0.12.0 // indirect github.com/nishanths/predeclared v0.2.2 // indirect github.com/nunnatsa/ginkgolinter v0.16.2 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/polyfloyd/go-errorlint v1.6.0 // indirect github.com/prometheus/client_golang v1.19.0 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.48.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1 // indirect github.com/quasilyte/go-ruleguard/dsl v0.3.22 // indirect github.com/quasilyte/gogrep v0.5.0 // indirect github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/ryancurrah/gomodguard v1.3.5 // indirect github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sanity-io/litter v1.5.5 // indirect github.com/sanposhiho/wastedassign/v2 v2.0.7 // indirect github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect github.com/sashamelentyev/interfacebloat v1.1.0 // indirect github.com/sashamelentyev/usestdlibvars v1.27.0 // indirect github.com/securego/gosec/v2 v2.21.2 // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/sivchari/containedctx v1.0.3 // indirect github.com/sivchari/tenv v1.10.0 // indirect github.com/skeema/knownhosts v1.2.2 // indirect github.com/sonatard/noctx v0.0.2 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/sourcegraph/go-diff v0.7.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect github.com/spf13/cobra v1.8.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/viper v1.19.0 // indirect github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect github.com/stbenjam/no-sprintf-host-port v0.1.1 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/stretchr/testify v1.9.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/tdakkota/asciicheck v0.2.0 // indirect github.com/tetafro/godot v1.4.17 // indirect github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 // indirect github.com/timonwong/loggercheck v0.9.4 // indirect github.com/tomarrell/wrapcheck/v2 v2.9.0 // indirect github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect github.com/ultraware/funlen v0.1.0 // indirect github.com/ultraware/whitespace v0.1.1 // indirect github.com/uudashr/gocognit v1.1.3 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xen0n/gosmopolitan v1.2.2 // indirect github.com/yagipy/maintidx v1.0.0 // indirect github.com/yeya24/promlinter v0.3.0 // indirect github.com/ykadowak/zerologlint v0.1.5 // indirect gitlab.com/bosi/decorder v0.4.2 // indirect go-simpler.org/musttag v0.12.2 // indirect go-simpler.org/sloglint v0.7.2 // indirect go.opentelemetry.io/build-tools v0.14.0 // indirect go.uber.org/automaxprocs v1.5.3 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect golang.org/x/crypto v0.28.0 // indirect golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f // indirect golang.org/x/mod v0.21.0 // indirect golang.org/x/net v0.30.0 // indirect golang.org/x/sync v0.8.0 // indirect golang.org/x/sys v0.26.0 // indirect golang.org/x/telemetry v0.0.0-20240522233618-39ace7a40ae7 // indirect golang.org/x/text v0.19.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect honnef.co/go/tools v0.5.1 // indirect mvdan.cc/gofumpt v0.7.0 // indirect mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f // indirect ) open-telemetry-opentelemetry-go-contrib-e5abccb/tools/go.sum000066400000000000000000001700571470323427300245560ustar00rootroot000000000000004d63.com/gocheckcompilerdirectives v1.2.1 h1:AHcMYuw56NPjq/2y615IGg2kYkBdTvOaojYCBcRE7MA= 4d63.com/gocheckcompilerdirectives v1.2.1/go.mod h1:yjDJSxmDTtIHHCqX0ufRYZDL6vQtMG7tJdKVeWwsqvs= 4d63.com/gochecknoglobals v0.2.1 h1:1eiorGsgHOFOuoOiJDy2psSrQbRdIHrlge0IJIkUgDc= 4d63.com/gochecknoglobals v0.2.1/go.mod h1:KRE8wtJB3CXCsb1xy421JfTHIIbmT3U5ruxw2Qu8fSU= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/4meepo/tagalign v1.3.4 h1:P51VcvBnf04YkHzjfclN6BbsopfJR5rxs1n+5zHt+w8= github.com/4meepo/tagalign v1.3.4/go.mod h1:M+pnkHH2vG8+qhE5bVc/zeP7HS/j910Fwa9TUSyZVI0= github.com/Abirdcfly/dupword v0.1.1 h1:Bsxe0fIw6OwBtXMIncaTxCLHYO5BB+3mcsR5E8VXloY= github.com/Abirdcfly/dupword v0.1.1/go.mod h1:B49AcJdTYYkpd4HjgAcutNGG9HZ2JWwKunH9Y2BA6sM= github.com/Antonboom/errname v0.1.13 h1:JHICqsewj/fNckzrfVSe+T33svwQxmjC+1ntDsHOVvM= github.com/Antonboom/errname v0.1.13/go.mod h1:uWyefRYRN54lBg6HseYCFhs6Qjcy41Y3Jl/dVhA87Ns= github.com/Antonboom/nilnil v0.1.9 h1:eKFMejSxPSA9eLSensFmjW2XTgTwJMjZ8hUHtV4s/SQ= github.com/Antonboom/nilnil v0.1.9/go.mod h1:iGe2rYwCq5/Me1khrysB4nwI7swQvjclR8/YRPl5ihQ= github.com/Antonboom/testifylint v1.4.3 h1:ohMt6AHuHgttaQ1xb6SSnxCeK4/rnK7KKzbvs7DmEck= github.com/Antonboom/testifylint v1.4.3/go.mod h1:+8Q9+AOLsz5ZiQiiYujJKs9mNz398+M6UgslP4qgJLA= github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs= github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/Crocmagnon/fatcontext v0.5.2 h1:vhSEg8Gqng8awhPju2w7MKHqMlg4/NI+gSDHtR3xgwA= github.com/Crocmagnon/fatcontext v0.5.2/go.mod h1:87XhRMaInHP44Q7Tlc7jkgKKB7kZAOPiDkFMdKCC+74= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rWPdisA5ynNEsoARbiCBOyGcJM4/OzsM= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0 h1:/fTUt5vmbkAcMBt4YQiuC23cV0kEsN1MVMNqeOW43cU= github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0/go.mod h1:ONJg5sxcbsdQQ4pOW8TGdTidT2TMAUy/2Xhr8mrYaao= github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/OpenPeeDeeP/depguard/v2 v2.2.0 h1:vDfG60vDtIuf0MEOhmLlLLSzqaRM8EMcgJPdp74zmpA= github.com/OpenPeeDeeP/depguard/v2 v2.2.0/go.mod h1:CIzddKRvLBC4Au5aYP/i3nyaWQ+ClszLIuVocRiCYFQ= github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78= github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= github.com/alecthomas/assert/v2 v2.2.2 h1:Z/iVC0xZfWTaFNE6bA3z07T86hd45Xe2eLt6WVy2bbk= github.com/alecthomas/assert/v2 v2.2.2/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= github.com/alecthomas/go-check-sumtype v0.1.4 h1:WCvlB3l5Vq5dZQTFmodqL2g68uHiSwwlWcT5a2FGK0c= github.com/alecthomas/go-check-sumtype v0.1.4/go.mod h1:WyYPfhfkdhyrdaligV6svFopZV8Lqdzn5pyVBaV6jhQ= github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk= github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/alexkohler/nakedret/v2 v2.0.4 h1:yZuKmjqGi0pSmjGpOC016LtPJysIL0WEUiaXW5SUnNg= github.com/alexkohler/nakedret/v2 v2.0.4/go.mod h1:bF5i0zF2Wo2o4X4USt9ntUWve6JbFv02Ff4vlkmS/VU= github.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pOcUuw= github.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cvfHR56wJVlKE= github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQRnw= github.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/ashanbrown/forbidigo v1.6.0 h1:D3aewfM37Yb3pxHujIPSpTf6oQk9sc9WZi8gerOIVIY= github.com/ashanbrown/forbidigo v1.6.0/go.mod h1:Y8j9jy9ZYAEHXdu723cUlraTqbzjKF1MUyfOKL+AjcU= github.com/ashanbrown/makezero v1.1.1 h1:iCQ87C0V0vSyO+M9E/FZYbu65auqH0lnsOkf5FcB28s= github.com/ashanbrown/makezero v1.1.1/go.mod h1:i1bJLCRSCHOcOa9Y6MyF2FTfMZMFdHvxKHxgO5Z1axI= github.com/atombender/go-jsonschema v0.16.0 h1:1C6jMVzAQ4RZCBwGQYMEVZvjSBdKUw/7arkhHPS0ldg= github.com/atombender/go-jsonschema v0.16.0/go.mod h1:qvHiMeC+Obu1QJTtD+rZGogD+Nn4QCztDJ0UNF8dBfs= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bkielbasa/cyclop v1.2.1 h1:AeF71HZDob1P2/pRm1so9cd1alZnrpyc4q2uP2l0gJY= github.com/bkielbasa/cyclop v1.2.1/go.mod h1:K/dT/M0FPAiYjBgQGau7tz+3TMh4FWAEqlMhzFWCrgM= github.com/blizzy78/varnamelen v0.8.0 h1:oqSblyuQvFsW1hbBHh1zfwrKe3kcSj0rnXkKzsQ089M= github.com/blizzy78/varnamelen v0.8.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkApdHeyESmyR7k= github.com/bombsimon/wsl/v4 v4.4.1 h1:jfUaCkN+aUpobrMO24zwyAMwMAV5eSziCkOKEauOLdw= github.com/bombsimon/wsl/v4 v4.4.1/go.mod h1:Xu/kDxGZTofQcDGCtQe9KCzhHphIe0fDuyWTxER9Feo= github.com/breml/bidichk v0.2.7 h1:dAkKQPLl/Qrk7hnP6P+E0xOodrq8Us7+U0o4UBOAlQY= github.com/breml/bidichk v0.2.7/go.mod h1:YodjipAGI9fGcYM7II6wFvGhdMYsC5pHDlGzqvEW3tQ= github.com/breml/errchkjson v0.3.6 h1:VLhVkqSBH96AvXEyclMR37rZslRrY2kcyq+31HCsVrA= github.com/breml/errchkjson v0.3.6/go.mod h1:jhSDoFheAF2RSDOlCfhHO9KqhZgAYLyvHe7bRCX8f/U= github.com/butuzov/ireturn v0.3.0 h1:hTjMqWw3y5JC3kpnC5vXmFJAWI/m31jaCYQqzkS6PL0= github.com/butuzov/ireturn v0.3.0/go.mod h1:A09nIiwiqzN/IoVo9ogpa0Hzi9fex1kd9PSD6edP5ZA= github.com/butuzov/mirror v1.2.0 h1:9YVK1qIjNspaqWutSv8gsge2e/Xpq1eqEkslEUHy5cs= github.com/butuzov/mirror v1.2.0/go.mod h1:DqZZDtzm42wIAIyHXeN8W/qb1EPlb9Qn/if9icBOpdQ= github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/catenacyber/perfsprint v0.7.1 h1:PGW5G/Kxn+YrN04cRAZKC+ZuvlVwolYMrIyyTJ/rMmc= github.com/catenacyber/perfsprint v0.7.1/go.mod h1:/wclWYompEyjUD2FuIIDVKNkqz7IgBIWXIH3V0Zol50= github.com/ccojocar/zxcvbn-go v1.0.2 h1:na/czXU8RrhXO4EZme6eQJLR4PzcGsahsBOAwU6I3Vg= github.com/ccojocar/zxcvbn-go v1.0.2/go.mod h1:g1qkXtUSvHP8lhHp5GrSmTz6uWALGRMQdw6Qnz/hi60= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/charithe/durationcheck v0.0.10 h1:wgw73BiocdBDQPik+zcEoBG/ob8uyBHf2iyoHGPf5w4= github.com/charithe/durationcheck v0.0.10/go.mod h1:bCWXb7gYRysD1CU3C+u4ceO49LoGOY1C1L6uouGNreQ= github.com/chavacava/garif v0.1.0 h1:2JHa3hbYf5D9dsgseMKAmc/MZ109otzgNFk5s87H9Pc= github.com/chavacava/garif v0.1.0/go.mod h1:XMyYCkEL58DF0oyW4qDjjnPWONs2HBqYKI+UIPD+Gww= github.com/ckaznocha/intrange v0.2.0 h1:FykcZuJ8BD7oX93YbO1UY9oZtkRbp+1/kJcDjkefYLs= github.com/ckaznocha/intrange v0.2.0/go.mod h1:r5I7nUlAAG56xmkOpw4XVr16BXhwYTUdcuRFeevn1oE= github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/curioswitch/go-reassign v0.2.0 h1:G9UZyOcpk/d7Gd6mqYgd8XYWFMw/znxwGDUstnC9DIo= github.com/curioswitch/go-reassign v0.2.0/go.mod h1:x6OpXuWvgfQaMGks2BZybTngWjT84hqJfKoO8Tt/Roc= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/daixiang0/gci v0.13.5 h1:kThgmH1yBmZSBCh1EJVxQ7JsHpm5Oms0AMed/0LaH4c= github.com/daixiang0/gci v0.13.5/go.mod h1:12etP2OniiIdP4q+kjUGrC/rUagga7ODbqsom5Eo5Yk= github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42t4429eC9k8= github.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY= github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/ettle/strcase v0.2.0 h1:fGNiVF21fHXpX1niBgk0aROov1LagYsOwV/xqKDKR/Q= github.com/ettle/strcase v0.2.0/go.mod h1:DajmHElDSaX76ITe3/VHVyMin4LWSJN5Z909Wp+ED1A= github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= github.com/firefart/nonamedreturns v1.0.5 h1:tM+Me2ZaXs8tfdDw3X6DOX++wMCOqzYUho6tUTYIdRA= github.com/firefart/nonamedreturns v1.0.5/go.mod h1:gHJjDqhGM4WyPt639SOZs+G89Ko7QKH5R5BhnO6xJhw= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= github.com/ghostiam/protogetter v0.3.6 h1:R7qEWaSgFCsy20yYHNIJsU9ZOb8TziSRRxuAOTVKeOk= github.com/ghostiam/protogetter v0.3.6/go.mod h1:7lpeDnEJ1ZjL/YtyoN99ljO4z0pd3H0d18/t2dPBxHw= github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE= github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8= github.com/go-critic/go-critic v0.11.4 h1:O7kGOCx0NDIni4czrkRIXTnit0mkyKOCePh3My6OyEU= github.com/go-critic/go-critic v0.11.4/go.mod h1:2QAdo4iuLik5S9YG0rT4wcZ8QxwHYkrr6/2MWAiv/vc= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys= github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-toolsmith/astcast v1.1.0 h1:+JN9xZV1A+Re+95pgnMgDboWNVnIMMQXwfBwLRPgSC8= github.com/go-toolsmith/astcast v1.1.0/go.mod h1:qdcuFWeGGS2xX5bLM/c3U9lewg7+Zu4mr+xPwZIB4ZU= github.com/go-toolsmith/astcopy v1.1.0 h1:YGwBN0WM+ekI/6SS6+52zLDEf8Yvp3n2seZITCUBt5s= github.com/go-toolsmith/astcopy v1.1.0/go.mod h1:hXM6gan18VA1T/daUEHCFcYiW8Ai1tIwIzHY6srfEAw= github.com/go-toolsmith/astequal v1.0.3/go.mod h1:9Ai4UglvtR+4up+bAD4+hCj7iTo4m/OXVTSLnCyTAx4= github.com/go-toolsmith/astequal v1.1.0/go.mod h1:sedf7VIdCL22LD8qIvv7Nn9MuWJruQA/ysswh64lffQ= github.com/go-toolsmith/astequal v1.2.0 h1:3Fs3CYZ1k9Vo4FzFhwwewC3CHISHDnVUPC4x0bI2+Cw= github.com/go-toolsmith/astequal v1.2.0/go.mod h1:c8NZ3+kSFtFY/8lPso4v8LuJjdJiUFVnSuU3s0qrrDY= github.com/go-toolsmith/astfmt v1.1.0 h1:iJVPDPp6/7AaeLJEruMsBUlOYCmvg0MoCfJprsOmcco= github.com/go-toolsmith/astfmt v1.1.0/go.mod h1:OrcLlRwu0CuiIBp/8b5PYF9ktGVZUjlNMV634mhwuQ4= github.com/go-toolsmith/astp v1.1.0 h1:dXPuCl6u2llURjdPLLDxJeZInAeZ0/eZwFJmqZMnpQA= github.com/go-toolsmith/astp v1.1.0/go.mod h1:0T1xFGz9hicKs8Z5MfAqSUitoUYS30pDMsRVIDHs8CA= github.com/go-toolsmith/pkgload v1.2.2 h1:0CtmHq/02QhxcF7E9N5LIFcYFsMR5rdovfqTtRKkgIk= github.com/go-toolsmith/pkgload v1.2.2/go.mod h1:R2hxLNRKuAsiXCo2i5J6ZQPhnPMOVtU+f0arbFPWCus= github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= github.com/go-toolsmith/strparse v1.1.0 h1:GAioeZUK9TGxnLS+qfdqNbA4z0SSm5zVNtCQiyP2Bvw= github.com/go-toolsmith/strparse v1.1.0/go.mod h1:7ksGy58fsaQkGQlY8WVoBFNyEPMGuJin1rfoPS4lBSQ= github.com/go-toolsmith/typep v1.1.0 h1:fIRYDyF+JywLfqzyhdiHzRop/GQDxxNhLGQ6gFUNHus= github.com/go-toolsmith/typep v1.1.0/go.mod h1:fVIw+7zjdsMxDA3ITWnH1yOiw1rnTQKCsF/sk2H/qig= github.com/go-viper/mapstructure/v2 v2.1.0 h1:gHnMa2Y/pIxElCH2GlZZ1lZSsn6XMtufpGyP1XxdC/w= github.com/go-viper/mapstructure/v2 v2.1.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/go-xmlfmt/xmlfmt v1.1.2 h1:Nea7b4icn8s57fTx1M5AI4qQT5HEM3rVUO8MuE6g80U= github.com/go-xmlfmt/xmlfmt v1.1.2/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/goccy/go-yaml v1.11.3 h1:B3W9IdWbvrUu2OYQGwvU1nZtvMQJPBKgBUuweJjLj6I= github.com/goccy/go-yaml v1.11.3/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU= github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a h1:w8hkcTqaFpzKqonE9uMCefW1WDie15eSP/4MssdenaM= github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= github.com/golangci/gofmt v0.0.0-20240816233607-d8596aa466a9 h1:/1322Qns6BtQxUZDTAT4SdcoxknUki7IAoK4SAXr8ME= github.com/golangci/gofmt v0.0.0-20240816233607-d8596aa466a9/go.mod h1:Oesb/0uFAyWoaw1U1qS5zyjCg5NP9C9iwjnI4tIsXEE= github.com/golangci/golangci-lint v1.61.0 h1:VvbOLaRVWmyxCnUIMTbf1kDsaJbTzH20FAMXTAlQGu8= github.com/golangci/golangci-lint v1.61.0/go.mod h1:e4lztIrJJgLPhWvFPDkhiMwEFRrWlmFbrZea3FsJyN8= github.com/golangci/misspell v0.6.0 h1:JCle2HUTNWirNlDIAUO44hUsKhOFqGPoC4LZxlaSXDs= github.com/golangci/misspell v0.6.0/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo= github.com/golangci/modinfo v0.3.4 h1:oU5huX3fbxqQXdfspamej74DFX0kyGLkw1ppvXoJ8GA= github.com/golangci/modinfo v0.3.4/go.mod h1:wytF1M5xl9u0ij8YSvhkEVPP3M5Mc7XLl1pxH3B2aUM= github.com/golangci/plugin-module-register v0.1.1 h1:TCmesur25LnyJkpsVrupv1Cdzo+2f7zX0H6Jkw1Ol6c= github.com/golangci/plugin-module-register v0.1.1/go.mod h1:TTpqoB6KkwOJMV8u7+NyXMrkwwESJLOkfl9TxR1DGFc= github.com/golangci/revgrep v0.5.3 h1:3tL7c1XBMtWHHqVpS5ChmiAAoe4PF/d5+ULzV9sLAzs= github.com/golangci/revgrep v0.5.3/go.mod h1:U4R/s9dlXZsg8uJmaR1GrloUr14D7qDl8gi2iPXJH8k= github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed h1:IURFTjxeTfNFP0hTEi1YKjB/ub8zkpaOqFFMApi2EAs= github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed/go.mod h1:XLXN8bNw4CGRPaqgl3bv/lhz7bsGPh4/xSaMTbo2vkQ= github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786 h1:rcv+Ippz6RAtvaGgKxc+8FQIpxHgsF+HBzPyYL2cyVU= github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786/go.mod h1:apVn/GCasLZUVpAJ6oWAuyP7Ne7CEsQbTnc0plM3m+o= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5 h1:5iH8iuqE5apketRbSFBy+X1V0o+l+8NF1avt4HWl7cA= github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/gordonklaus/ineffassign v0.1.0 h1:y2Gd/9I7MdY1oEIt+n+rowjBNDcLQq3RsH5hwJd0f9s= github.com/gordonklaus/ineffassign v0.1.0/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0= github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk= github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc= github.com/gostaticanalysis/comment v1.4.1/go.mod h1:ih6ZxzTHLdadaiSnF5WY3dxUoXfXAlTaRzuaNDlSado= github.com/gostaticanalysis/comment v1.4.2 h1:hlnx5+S2fY9Zo9ePo4AhgYsYHbM2+eAv8m/s1JiCd6Q= github.com/gostaticanalysis/comment v1.4.2/go.mod h1:KLUTGDv6HOCotCH8h2erHKmpci2ZoR8VPu34YA2uzdM= github.com/gostaticanalysis/forcetypeassert v0.1.0 h1:6eUflI3DiGusXGK6X7cCcIgVCpZ2CiZ1Q7jl6ZxNV70= github.com/gostaticanalysis/forcetypeassert v0.1.0/go.mod h1:qZEedyP/sY1lTGV1uJ3VhWZ2mqag3IkWsDHVbplHXak= github.com/gostaticanalysis/nilerr v0.1.1 h1:ThE+hJP0fEp4zWLkWHWcRyI2Od0p7DlgYG3Uqrmrcpk= github.com/gostaticanalysis/nilerr v0.1.1/go.mod h1:wZYb6YI5YAxxq0i1+VJbY0s2YONW0HU0GPE3+5PWN4A= github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M= github.com/gostaticanalysis/testutil v0.4.0 h1:nhdCmubdmDF6VEatUNjgUZBJKWRqugoISdUv3PPQgHY= github.com/gostaticanalysis/testutil v0.4.0/go.mod h1:bLIoPefWXrRi/ssLFWX1dx7Repi5x3CuviD3dgAZaBU= github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/itchyny/gojq v0.12.16 h1:yLfgLxhIr/6sJNVmYfQjTIv0jGctu6/DgDoivmxTr7g= github.com/itchyny/gojq v0.12.16/go.mod h1:6abHbdC2uB9ogMS38XsErnfqJ94UlngIJGlRAIj4jTM= github.com/itchyny/timefmt-go v0.1.6 h1:ia3s54iciXDdzWzwaVKXZPbiXzxxnv1SPGFfM/myJ5Q= github.com/itchyny/timefmt-go v0.1.6/go.mod h1:RRDZYC5s9ErkjQvTvvU7keJjxUYzIISJGxm9/mAERQg= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jcchavezs/porto v0.6.0 h1:AgQLGwsXaxDkPj4Y+paFkVGLAR4n/1RRF0xV5UKinwg= github.com/jcchavezs/porto v0.6.0/go.mod h1:fESH0gzDHiutHRdX2hv27ojnOVFco37hg1W6E9EZF4A= github.com/jgautheron/goconst v1.7.1 h1:VpdAG7Ca7yvvJk5n8dMwQhfEZJh95kl/Hl9S1OI5Jkk= github.com/jgautheron/goconst v1.7.1/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4= github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs= github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af h1:KA9BjwUk7KlCh6S9EAGWBt1oExIUv9WyNCiRz5amv48= github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0= github.com/jjti/go-spancheck v0.6.2 h1:iYtoxqPMzHUPp7St+5yA8+cONdyXD3ug6KK15n7Pklk= github.com/jjti/go-spancheck v0.6.2/go.mod h1:+X7lvIrR5ZdUTkxFYqzJ0abr8Sb5LOo80uOhWNqIrYA= github.com/julz/importas v0.1.0 h1:F78HnrsjY3cR7j0etXy5+TU1Zuy7Xt08X/1aJnH5xXY= github.com/julz/importas v0.1.0/go.mod h1:oSFU2R4XK/P7kNBrnL/FEQlDGN1/6WoxXEjSSXO0DV0= github.com/karamaru-alpha/copyloopvar v1.1.0 h1:x7gNyKcC2vRBO1H2Mks5u1VxQtYvFiym7fCjIP8RPos= github.com/karamaru-alpha/copyloopvar v1.1.0/go.mod h1:u7CIfztblY0jZLOQZgH3oYsJzpC2A7S6u/lfgSXHy0k= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.7.0 h1:+SbscKmWJ5mOK/bO1zS60F5I9WwZDWOfRsC4RwfwRV0= github.com/kisielk/errcheck v1.7.0/go.mod h1:1kLL+jV4e+CFfueBmI1dSK2ADDyQnlrnrY/FqKluHJQ= github.com/kkHAIKE/contextcheck v1.1.5 h1:CdnJh63tcDe53vG+RebdpdXJTc9atMgGqdx8LXxiilg= github.com/kkHAIKE/contextcheck v1.1.5/go.mod h1:O930cpht4xb1YQpK+1+AgoM3mFsvxr7uyFptcnWTYUA= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kulti/thelper v0.6.3 h1:ElhKf+AlItIu+xGnI990no4cE2+XaSu1ULymV2Yulxs= github.com/kulti/thelper v0.6.3/go.mod h1:DsqKShOvP40epevkFrvIwkCMNYxMeTNjdWL4dqWHZ6I= github.com/kunwardeep/paralleltest v1.0.10 h1:wrodoaKYzS2mdNVnc4/w31YaXFtsc21PCTdvWJ/lDDs= github.com/kunwardeep/paralleltest v1.0.10/go.mod h1:2C7s65hONVqY7Q5Efj5aLzRCNLjw2h4eMc9EcypGjcY= github.com/kyoh86/exportloopref v0.1.11 h1:1Z0bcmTypkL3Q4k+IDHMWTcnCliEZcaPiIe0/ymEyhQ= github.com/kyoh86/exportloopref v0.1.11/go.mod h1:qkV4UF1zGl6EkF1ox8L5t9SwyeBAZ3qLMd6up458uqA= github.com/lasiar/canonicalheader v1.1.1 h1:wC+dY9ZfiqiPwAexUApFush/csSPXeIi4QqyxXmng8I= github.com/lasiar/canonicalheader v1.1.1/go.mod h1:cXkb3Dlk6XXy+8MVQnF23CYKWlyA7kfQhSw2CcZtZb0= github.com/ldez/gomoddirectives v0.2.4 h1:j3YjBIjEBbqZ0NKtBNzr8rtMHTOrLPeiwTkfUJZ3alg= github.com/ldez/gomoddirectives v0.2.4/go.mod h1:oWu9i62VcQDYp9EQ0ONTfqLNh+mDLWWDO+SO0qSQw5g= github.com/ldez/tagliatelle v0.5.0 h1:epgfuYt9v0CG3fms0pEgIMNPuFf/LpPIfjk4kyqSioo= github.com/ldez/tagliatelle v0.5.0/go.mod h1:rj1HmWiL1MiKQuOONhd09iySTEkUuE/8+5jtPYz9xa4= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84YrjT3mIY= github.com/leonklingele/grouper v1.1.2/go.mod h1:6D0M/HVkhs2yRKRFZUoGjeDy7EZTfFBE9gl4kjmIGkA= github.com/lufeee/execinquery v1.2.1 h1:hf0Ems4SHcUGBxpGN7Jz78z1ppVkP/837ZlETPCEtOM= github.com/lufeee/execinquery v1.2.1/go.mod h1:EC7DrEKView09ocscGHC+apXMIaorh4xqSxS/dy8SbM= github.com/macabu/inamedparam v0.1.3 h1:2tk/phHkMlEL/1GNe/Yf6kkR/hkcUdAEY3L0hjYV1Mk= github.com/macabu/inamedparam v0.1.3/go.mod h1:93FLICAIk/quk7eaPPQvbzihUdn/QkGDwIZEoLtpH6I= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/maratori/testableexamples v1.0.0 h1:dU5alXRrD8WKSjOUnmJZuzdxWOEQ57+7s93SLMxb2vI= github.com/maratori/testableexamples v1.0.0/go.mod h1:4rhjL1n20TUTT4vdh3RDqSizKLyXp7K2u6HgraZCGzE= github.com/maratori/testpackage v1.1.1 h1:S58XVV5AD7HADMmD0fNnziNHqKvSdDuEKdPD1rNTU04= github.com/maratori/testpackage v1.1.1/go.mod h1:s4gRK/ym6AMrqpOa/kEbQTV4Q4jb7WeLZzVhVVVOQMc= github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 h1:gWg6ZQ4JhDfJPqlo2srm/LN17lpybq15AryXIRcWYLE= github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s= github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mgechev/revive v1.3.9 h1:18Y3R4a2USSBF+QZKFQwVkBROUda7uoBlkEuBD+YD1A= github.com/mgechev/revive v1.3.9/go.mod h1:+uxEIr5UH0TjXWHTno3xh4u7eg6jDpXKzQccA9UGhHU= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/moricho/tparallel v0.3.2 h1:odr8aZVFA3NZrNybggMkYO3rgPRcqjeQUlBBFVxKHTI= github.com/moricho/tparallel v0.3.2/go.mod h1:OQ+K3b4Ln3l2TZveGCywybl68glfLEwFGqvnjok8b+U= github.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U= github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE= github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhKRf3Swg= github.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs= github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk= github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c= github.com/nunnatsa/ginkgolinter v0.16.2 h1:8iLqHIZvN4fTLDC0Ke9tbSZVcyVHoBs0HIbnVSxfHJk= github.com/nunnatsa/ginkgolinter v0.16.2/go.mod h1:4tWRinDN1FeJgU+iJANW/kz7xKN5nYRAOfJDQUS9dOQ= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4= github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag= github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8= github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc= github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/polyfloyd/go-errorlint v1.6.0 h1:tftWV9DE7txiFzPpztTAwyoRLKNj9gpVm2cg8/OwcYY= github.com/polyfloyd/go-errorlint v1.6.0/go.mod h1:HR7u8wuP1kb1NeN1zqTd1ZMlqUKPPHF+Id4vIPvDqVw= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU= github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1 h1:+Wl/0aFp0hpuHM3H//KMft64WQ1yX9LdJY64Qm/gFCo= github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1/go.mod h1:GJLgqsLeo4qgavUoL8JeGFNS7qcisx3awV/w9eWTmNI= github.com/quasilyte/go-ruleguard/dsl v0.3.22 h1:wd8zkOhSNr+I+8Qeciml08ivDt1pSXe60+5DqOpCjPE= github.com/quasilyte/go-ruleguard/dsl v0.3.22/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= github.com/quasilyte/gogrep v0.5.0 h1:eTKODPXbI8ffJMN+W2aE0+oL0z/nh8/5eNdiO34SOAo= github.com/quasilyte/gogrep v0.5.0/go.mod h1:Cm9lpz9NZjEoL1tgZ2OgeUKPIxL1meE7eo60Z6Sk+Ng= github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 h1:TCg2WBOl980XxGFEZSS6KlBGIV0diGdySzxATTWoqaU= github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0= github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4lu7Gd+PU1fV2/qnDNfzT635KRSObncs= github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryancurrah/gomodguard v1.3.5 h1:cShyguSwUEeC0jS7ylOiG/idnd1TpJ1LfHGpV3oJmPU= github.com/ryancurrah/gomodguard v1.3.5/go.mod h1:MXlEPQRxgfPQa62O8wzK3Ozbkv9Rkqr+wKjSxTdsNJE= github.com/ryanrolds/sqlclosecheck v0.5.1 h1:dibWW826u0P8jNLsLN+En7+RqWWTYrjCB9fJfSfdyCU= github.com/ryanrolds/sqlclosecheck v0.5.1/go.mod h1:2g3dUjoS6AL4huFdv6wn55WpLIDjY7ZgUR4J8HOO/XQ= github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo= github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U= github.com/sanposhiho/wastedassign/v2 v2.0.7 h1:J+6nrY4VW+gC9xFzUc+XjPD3g3wF3je/NsJFwFK7Uxc= github.com/sanposhiho/wastedassign/v2 v2.0.7/go.mod h1:KyZ0MWTwxxBmfwn33zh3k1dmsbF2ud9pAAGfoLfjhtI= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tMEOsumirXcOJqAw= github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ= github.com/sashamelentyev/usestdlibvars v1.27.0 h1:t/3jZpSXtRPRf2xr0m63i32ZrusyurIGT9E5wAvXQnI= github.com/sashamelentyev/usestdlibvars v1.27.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8= github.com/securego/gosec/v2 v2.21.2 h1:deZp5zmYf3TWwU7A7cR2+SolbTpZ3HQiwFqnzQyEl3M= github.com/securego/gosec/v2 v2.21.2/go.mod h1:au33kg78rNseF5PwPnTWhuYBFf534bvJRvOrgZ/bFzU= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c h1:W65qqJCIOVP4jpqPQ0YvHYKwcMEMVWIzWC5iNQQfBTU= github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c/go.mod h1:/PevMnwAxekIXwN8qQyfc5gl2NlkB3CQlkizAbOkeBs= github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE= github.com/sivchari/containedctx v1.0.3/go.mod h1:c1RDvCbnJLtH4lLcYD/GqwiBSSf4F5Qk0xld2rBqzJ4= github.com/sivchari/tenv v1.10.0 h1:g/hzMA+dBCKqGXgW8AV/1xIWhAvDrx0zFKNR48NFMg0= github.com/sivchari/tenv v1.10.0/go.mod h1:tdY24masnVoZFxYrHv/nD6Tc8FbkEtAQEEziXpyMgqY= github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A= github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= github.com/sonatard/noctx v0.0.2 h1:L7Dz4De2zDQhW8S0t+KUjY0MAQJd6SgVwhzNIc4ok00= github.com/sonatard/noctx v0.0.2/go.mod h1:kzFz+CzWSjQ2OzIm46uJZoXuBpa2+0y3T36U18dWqIo= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/sourcegraph/go-diff v0.7.0 h1:9uLlrd5T46OXs5qpp8L/MTltk0zikUGi0sNNyCpA8G0= github.com/sourcegraph/go-diff v0.7.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= github.com/ssgreg/nlreturn/v2 v2.2.1 h1:X4XDI7jstt3ySqGU86YGAURbxw3oTDPK9sPEi6YEwQ0= github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= github.com/stbenjam/no-sprintf-host-port v0.1.1 h1:tYugd/yrm1O0dV+ThCbaKZh195Dfm07ysF0U6JQXczc= github.com/stbenjam/no-sprintf-host-port v0.1.1/go.mod h1:TLhvtIvONRzdmkFiio4O8LHsN9N74I+PhRquPsxpL0I= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/tdakkota/asciicheck v0.2.0 h1:o8jvnUANo0qXtnslk2d3nMKTFNlOnJjRrNcj0j9qkHM= github.com/tdakkota/asciicheck v0.2.0/go.mod h1:Qb7Y9EgjCLJGup51gDHFzbI08/gbGhL/UVhYIPWG2rg= github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA= github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0= github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpRQGxTSkNYKJ51yaw6ChIqO+Je8UqsTKN/cDag= github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= github.com/tetafro/godot v1.4.17 h1:pGzu+Ye7ZUEFx7LHU0dAKmCOXWsPjl7qA6iMGndsjPs= github.com/tetafro/godot v1.4.17/go.mod h1:2oVxTBSftRTh4+MVfUaUXR6bn2GDXCaMcOG4Dk3rfio= github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 h1:quvGphlmUVU+nhpFa4gg4yJyTRJ13reZMDHrKwYw53M= github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966/go.mod h1:27bSVNWSBOHm+qRp1T9qzaIpsWEP6TbUnei/43HK+PQ= github.com/timonwong/loggercheck v0.9.4 h1:HKKhqrjcVj8sxL7K77beXh0adEm6DLjV/QOGeMXEVi4= github.com/timonwong/loggercheck v0.9.4/go.mod h1:caz4zlPcgvpEkXgVnAJGowHAMW2NwHaNlpS8xDbVhTg= github.com/tomarrell/wrapcheck/v2 v2.9.0 h1:801U2YCAjLhdN8zhZ/7tdjB3EnAoRlJHt/s+9hijLQ4= github.com/tomarrell/wrapcheck/v2 v2.9.0/go.mod h1:g9vNIyhb5/9TQgumxQyOEqDHsmGYcGsVMOx/xGkqdMo= github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+yU8u1Zw= github.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw= github.com/ultraware/funlen v0.1.0 h1:BuqclbkY6pO+cvxoq7OsktIXZpgBSkYTQtmwhAK81vI= github.com/ultraware/funlen v0.1.0/go.mod h1:XJqmOQja6DpxarLj6Jj1U7JuoS8PvL4nEqDaQhy22p4= github.com/ultraware/whitespace v0.1.1 h1:bTPOGejYFulW3PkcrqkeQwOd6NKOOXvmGD9bo/Gk8VQ= github.com/ultraware/whitespace v0.1.1/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8= github.com/uudashr/gocognit v1.1.3 h1:l+a111VcDbKfynh+airAy/DJQKaXh2m9vkoysMPSZyM= github.com/uudashr/gocognit v1.1.3/go.mod h1:aKH8/e8xbTRBwjbCkwZ8qt4l2EpKXl31KMHgSS+lZ2U= github.com/wadey/gocovmerge v0.0.0-20160331181800-b5bfa59ec0ad h1:W0LEBv82YCGEtcmPA3uNZBI33/qF//HAAs3MawDjRa0= github.com/wadey/gocovmerge v0.0.0-20160331181800-b5bfa59ec0ad/go.mod h1:Hy8o65+MXnS6EwGElrSRjUzQDLXreJlzYLlWiHtt8hM= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xen0n/gosmopolitan v1.2.2 h1:/p2KTnMzwRexIW8GlKawsTWOxn7UHA+jCMF/V8HHtvU= github.com/xen0n/gosmopolitan v1.2.2/go.mod h1:7XX7Mj61uLYrj0qmeN0zi7XDon9JRAEhYQqAPLVNTeg= github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM= github.com/yagipy/maintidx v1.0.0/go.mod h1:0qNf/I/CCZXSMhsRsrEPDZ+DkekpKLXAJfsTACwgXLk= github.com/yeya24/promlinter v0.3.0 h1:JVDbMp08lVCP7Y6NP3qHroGAO6z2yGKQtS5JsjqtoFs= github.com/yeya24/promlinter v0.3.0/go.mod h1:cDfJQQYv9uYciW60QT0eeHlFodotkYZlL+YcPQN+mW4= github.com/ykadowak/zerologlint v0.1.5 h1:Gy/fMz1dFQN9JZTPjv1hxEk+sRWm05row04Yoolgdiw= github.com/ykadowak/zerologlint v0.1.5/go.mod h1:KaUskqF3e/v59oPmdq1U1DnKcuHokl2/K1U4pmIELKg= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= gitlab.com/bosi/decorder v0.4.2 h1:qbQaV3zgwnBZ4zPMhGLW4KZe7A7NwxEhJx39R3shffo= gitlab.com/bosi/decorder v0.4.2/go.mod h1:muuhHoaJkA9QLcYHq4Mj8FJUwDZ+EirSHRiaTcTf6T8= go-simpler.org/assert v0.9.0 h1:PfpmcSvL7yAnWyChSjOz6Sp6m9j5lyK8Ok9pEL31YkQ= go-simpler.org/assert v0.9.0/go.mod h1:74Eqh5eI6vCK6Y5l3PI8ZYFXG4Sa+tkr70OIPJAUr28= go-simpler.org/musttag v0.12.2 h1:J7lRc2ysXOq7eM8rwaTYnNrHd5JwjppzB6mScysB2Cs= go-simpler.org/musttag v0.12.2/go.mod h1:uN1DVIasMTQKk6XSik7yrJoEysGtR2GRqvWnI9S7TYM= go-simpler.org/sloglint v0.7.2 h1:Wc9Em/Zeuu7JYpl+oKoYOsQSy2X560aVueCW/m6IijY= go-simpler.org/sloglint v0.7.2/go.mod h1:US+9C80ppl7VsThQclkM7BkCHQAzuz8kHLsW3ppuluo= go.opentelemetry.io/build-tools v0.14.0 h1:fcnriXRUVpnVIFXtdlc1fTn9g+YRxzOV0xhw4nN919c= go.opentelemetry.io/build-tools v0.14.0/go.mod h1:pxTqOr0uL/0s9+xnpuKTAhmVFDssF3O4UUUuWKQqThE= go.opentelemetry.io/build-tools/crosslink v0.14.0 h1:yxCsELb3A81W4p8RSDjPSg9WcCTkM3+X+tYUzaaJ3uU= go.opentelemetry.io/build-tools/crosslink v0.14.0/go.mod h1:QJ+E5i4+CCg40jlOYQsfBq4lVe2cKCyhftEXDsqNlhg= go.opentelemetry.io/build-tools/gotmpl v0.14.0 h1:oCDzQvs78J0r311JZ60lcHmZ6xAK0O4F7gIHN8jeB2A= go.opentelemetry.io/build-tools/gotmpl v0.14.0/go.mod h1:jO5X6iTySwb3UMbiC380ZgwLPCdKyQwxEraFh4dYYF8= go.opentelemetry.io/build-tools/multimod v0.14.0 h1:AaM06mlSga3IaCj6eM+Kg9tei062qsU6Z+x6ENmfBWI= go.opentelemetry.io/build-tools/multimod v0.14.0/go.mod h1:lY7ZccnZ6dg4uRcghXa4p9v4IDvI9Yf/XFdlpPO84AA= go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20241004190924-225e2abe05e6 h1:1wqE9dj9NpSm04INVsJhhEUzhuDVjbcyKH91sVyPATw= golang.org/x/exp v0.0.0-20241004190924-225e2abe05e6/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8= golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f h1:phY1HzDcf18Aq9A8KkmRtY9WvOFIxN8wgfvy6Zm1DV8= golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/telemetry v0.0.0-20240522233618-39ace7a40ae7 h1:FemxDzfMUcK2f3YY4H+05K9CDzbSVr2+q/JKN45pey0= golang.org/x/telemetry v0.0.0-20240522233618-39ace7a40ae7/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190321232350-e250d351ecad/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200324003944-a576cf524670/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200329025819-fd4102a86c65/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200820010801-b793a1359eac/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20201023174141-c8cfbd0f21e6/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= golang.org/x/vuln v1.1.3 h1:NPGnvPOTgnjBc9HTaUx+nj+EaUYxl5SJOWqaDYGaFYw= golang.org/x/vuln v1.1.3/go.mod h1:7Le6Fadm5FOqE9C926BCD0g12NWyhg7cxV4BwcPFuNY= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.5.1 h1:4bH5o3b5ZULQ4UrBmP+63W9r7qIkqJClEA9ko5YKx+I= honnef.co/go/tools v0.5.1/go.mod h1:e9irvo83WDG9/irijV44wr3tbhcFeRnfpVlRqVwpzMs= mvdan.cc/gofumpt v0.7.0 h1:bg91ttqXmi9y2xawvkuMXyvAA/1ZGJqYAEGjXuP0JXU= mvdan.cc/gofumpt v0.7.0/go.mod h1:txVFJy/Sc/mvaycET54pV8SW8gWxTlUuGHVEcncmNUo= mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f h1:lMpcwN6GxNbWtbpI1+xzFLSW8XzX0u72NttUGVFjO3U= mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f/go.mod h1:RSLa7mKKCNeTTMHBw5Hsy2rfJmd6O2ivt9Dw9ZqCQpQ= open-telemetry-opentelemetry-go-contrib-e5abccb/tools/should_build.sh000077500000000000000000000012741470323427300264310ustar00rootroot00000000000000#!/bin/bash # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 # Returns 0 (true) when the current diff contains files in the provided # target directory. TARGET should be a unique package name in the directory # structure. For example, for the gocql integration, set TARGET=gocql so that # a diff in any of the files in the instrumentation/gocql/gocql directory # will be picked up by the grep. Diffs are compared against the main branch. TARGET=$1 if [ -z "$TARGET" ]; then echo "TARGET is undefined" exit 1 fi if git diff --name-only origin/main HEAD | grep -q "$TARGET"; then exit 0 else echo "no changes found for $TARGET. skipping tests..." exit 1 fi open-telemetry-opentelemetry-go-contrib-e5abccb/tools/tools.go000066400000000000000000000012521470323427300251000ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 //go:build tools // +build tools package tools // import "go.opentelemetry.io/contrib/tools" import ( _ "github.com/atombender/go-jsonschema" _ "github.com/client9/misspell/cmd/misspell" _ "github.com/golangci/golangci-lint/cmd/golangci-lint" _ "github.com/itchyny/gojq" _ "github.com/jcchavezs/porto/cmd/porto" _ "github.com/wadey/gocovmerge" _ "go.opentelemetry.io/build-tools/crosslink" _ "go.opentelemetry.io/build-tools/gotmpl" _ "go.opentelemetry.io/build-tools/multimod" _ "golang.org/x/exp/cmd/gorelease" _ "golang.org/x/tools/cmd/stringer" _ "golang.org/x/vuln/cmd/govulncheck" ) open-telemetry-opentelemetry-go-contrib-e5abccb/tools/verify_released_changelog.sh000077500000000000000000000026651470323427300311400ustar00rootroot00000000000000#!/bin/bash # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 set -euo pipefail TARGET="${1:?Must provide target ref}" FILE="CHANGELOG.md" TEMP_DIR=$(mktemp -d) echo "Temp folder: $TEMP_DIR" # Only the latest commit of the feature branch is available # automatically. To diff with the base branch, we need to # fetch that too (and we only need its latest commit). git fetch origin "${TARGET}" --depth=1 # Checkout the previous version on the base branch of the changelog to tmpfolder git --work-tree="$TEMP_DIR" checkout FETCH_HEAD $FILE PREVIOUS_FILE="$TEMP_DIR/$FILE" CURRENT_FILE="$FILE" PREVIOUS_LOCKED_FILE="$TEMP_DIR/previous_locked_section.md" CURRENT_LOCKED_FILE="$TEMP_DIR/current_locked_section.md" # Extract released sections from the previous version awk '/^/ {flag=1} /^/ {flag=0} flag' "$PREVIOUS_FILE" > "$PREVIOUS_LOCKED_FILE" # Extract released sections from the current version awk '/^/ {flag=1} /^/ {flag=0} flag' "$CURRENT_FILE" > "$CURRENT_LOCKED_FILE" # Compare the released sections if ! diff -q "$PREVIOUS_LOCKED_FILE" "$CURRENT_LOCKED_FILE"; then echo "Error: The released sections of the changelog file have been modified." diff "$PREVIOUS_LOCKED_FILE" "$CURRENT_LOCKED_FILE" rm -rf "$TEMP_DIR" false fi rm -rf "$TEMP_DIR" echo "The released sections remain unchanged." open-telemetry-opentelemetry-go-contrib-e5abccb/tools/version.go000066400000000000000000000007641470323427300254340ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package tools // import "go.opentelemetry.io/contrib/tools" // Version is the current release version of the OpenTelemetry Contrib tools. func Version() string { return "1.31.0" // This string is updated by the pre_release.sh script during release } // SemVersion is the semantic version to be supplied to tracer/meter creation. // // Deprecated: Use [Version] instead. func SemVersion() string { return Version() } open-telemetry-opentelemetry-go-contrib-e5abccb/tools/wait.sh000077500000000000000000000016201470323427300247130ustar00rootroot00000000000000#!/bin/bash # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 wait_for_cassandra () { for ((i = 0; i < 5; ++i)); do if docker exec "$1" nodetool status | grep "^UN"; then exit 0 fi echo "Cassandra not yet available" sleep 10 done echo "Timeout waiting for cassandra to initialize" exit 1 } wait_for_gomemcache () { for ((i = 0; i < 5; ++i)); do if nc -z localhost 11211; then exit 0 fi echo "Gomemcache not yet available..." sleep 10 done echo "Timeout waiting for gomemcache to initialize" exit 1 } if [ -z "$CMD" ]; then echo "CMD is undefined. exiting..." exit 1 elif [ -z "$IMG_NAME" ]; then echo "IMG_NAME is undefined. exiting..." exit 1 fi if [ "$CMD" == "cassandra" ]; then wait_for_cassandra "$IMG_NAME" elif [ "$CMD" == "gomemcache" ]; then wait_for_gomemcache else echo "unknown CMD" exit 1 fi open-telemetry-opentelemetry-go-contrib-e5abccb/verify_examples.sh000077500000000000000000000035571470323427300260240ustar00rootroot00000000000000#!/bin/bash # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 set -euo pipefail cd $(dirname $0) TOOLS_DIR=$(pwd)/.tools GOPATH=$(go env GOPATH) if [ -z "${GOPATH}" ] ; then printf "GOPATH is not defined.\n" exit -1 fi if [ ! -d "${GOPATH}" ] ; then printf "GOPATH ${GOPATH} is invalid \n" exit -1 fi # Pre-requisites if ! git diff --quiet; then \ git status printf "\n\nError: working tree is not clean\n" exit -1 fi if [ "$(git tag --contains $(git log -1 --pretty=format:"%H"))" = "" ] ; then printf "$(git log -1)" printf "\n\nError: HEAD is not pointing to a tagged version" fi make ${TOOLS_DIR}/gojq DIR_TMP="${GOPATH}/src/oteltmp/" rm -rf $DIR_TMP mkdir -p $DIR_TMP printf "Copy examples to ${DIR_TMP}\n" cp -a ./examples ${DIR_TMP} # Update go.mod files printf "Update go.mod: rename module and remove replace\n" PACKAGE_DIRS=$(find . -mindepth 2 -type f -name 'go.mod' -exec dirname {} \; | egrep 'examples' | sed 's/^\.\///' | sort) for dir in $PACKAGE_DIRS; do printf " Update go.mod for $dir\n" (cd "${DIR_TMP}/${dir}" && \ # replaces is ("mod1" "mod2" …) replaces=($(go mod edit -json | ${TOOLS_DIR}/gojq '.Replace[].Old.Path' || true)) # strip double quotes replaces=("${replaces[@]%\"}") && \ replaces=("${replaces[@]#\"}") && \ # make an array (-dropreplace=mod1 -dropreplace=mod2 …) dropreplaces=("${replaces[@]/#/-dropreplace=}") && \ go mod edit -module "oteltmp/${dir}" "${dropreplaces[@]}" && \ go mod tidy) done printf "Update done:\n\n" # Build directories that contain main package. These directories are different than # directories that contain go.mod files. printf "Build examples:\n" EXAMPLES=$(./get_main_pkgs.sh ./examples) for ex in $EXAMPLES; do printf " Build $ex in ${DIR_TMP}/${ex}\n" (cd "${DIR_TMP}/${ex}" && \ go build .) done # Cleanup printf "Remove copied files.\n" rm -rf $DIR_TMP open-telemetry-opentelemetry-go-contrib-e5abccb/version.go000066400000000000000000000011411470323427300242620ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package contrib contains common values used across all // instrumentation, exporter, and detector contributions. package contrib // import "go.opentelemetry.io/contrib" // Version is the current release version of OpenTelemetry Contrib in use. func Version() string { return "1.31.0" // This string is updated by the pre_release.sh script during release } // SemVersion is the semantic version to be supplied to tracer/meter creation. // // Deprecated: Use [Version] instead. func SemVersion() string { return Version() } open-telemetry-opentelemetry-go-contrib-e5abccb/versions.yaml000066400000000000000000000124771470323427300250200ustar00rootroot00000000000000# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 module-sets: stable-v1: version: v1.31.0 modules: - go.opentelemetry.io/contrib - go.opentelemetry.io/contrib/tools - go.opentelemetry.io/contrib/propagators/aws - go.opentelemetry.io/contrib/propagators/ot - go.opentelemetry.io/contrib/propagators/jaeger - go.opentelemetry.io/contrib/propagators/b3 - go.opentelemetry.io/contrib/detectors/gcp - go.opentelemetry.io/contrib/detectors/aws/ec2 - go.opentelemetry.io/contrib/detectors/aws/ecs - go.opentelemetry.io/contrib/detectors/aws/eks experimental-instrumentation: version: v0.56.0 modules: - go.opentelemetry.io/contrib/bridges/prometheus - go.opentelemetry.io/contrib/detectors/aws/lambda - go.opentelemetry.io/contrib/exporters/autoexport - go.opentelemetry.io/contrib/propagators/autoprop - go.opentelemetry.io/contrib/propagators/opencensus - go.opentelemetry.io/contrib/propagators/opencensus/examples - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/example - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/test - go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace - go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace/example - go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace/test - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/example - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/test - go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo - go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo/test - go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux - go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux/example - go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux/test - go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin - go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin/example - go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin/test - go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho - go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho/example - go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho/test - go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda/xrayconfig - go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda - go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda/example - go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-lambda-go/otellambda/test - go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws - go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/example - go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws/test - go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful - go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful/example - go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful/test - go.opentelemetry.io/contrib/instrumentation/host - go.opentelemetry.io/contrib/instrumentation/host/example - go.opentelemetry.io/contrib/instrumentation/runtime - go.opentelemetry.io/contrib/zpages experimental-samplers: version: v0.25.0 modules: - go.opentelemetry.io/contrib/samplers/jaegerremote - go.opentelemetry.io/contrib/samplers/jaegerremote/example - go.opentelemetry.io/contrib/samplers/probability/consistent experimental-config: version: v0.11.0 modules: - go.opentelemetry.io/contrib/config experimental-bridge: version: v0.6.0 modules: - go.opentelemetry.io/contrib/bridges/otelslog - go.opentelemetry.io/contrib/bridges/otellogrus - go.opentelemetry.io/contrib/bridges/otelzap experimental-processors: version: v0.4.0 modules: - go.opentelemetry.io/contrib/processors/baggagecopy - go.opentelemetry.io/contrib/processors/minsev experimental-detectors: version: v0.3.0 modules: - go.opentelemetry.io/contrib/detectors/azure/azurevm excluded-modules: - go.opentelemetry.io/contrib/bridges/otellogr - go.opentelemetry.io/contrib/examples/dice - go.opentelemetry.io/contrib/examples/namedtracer - go.opentelemetry.io/contrib/examples/opencensus - go.opentelemetry.io/contrib/examples/otel-collector - go.opentelemetry.io/contrib/examples/passthrough - go.opentelemetry.io/contrib/examples/prometheus - go.opentelemetry.io/contrib/examples/zipkin - go.opentelemetry.io/contrib/instrgen - go.opentelemetry.io/contrib/instrgen/driver - go.opentelemetry.io/contrib/instrgen/testdata/interface open-telemetry-opentelemetry-go-contrib-e5abccb/zpages/000077500000000000000000000000001470323427300235425ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/zpages/boundaries.go000066400000000000000000000023101470323427300262200ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package zpages // import "go.opentelemetry.io/contrib/zpages" import ( "sort" "time" ) const ( zeroDuration = time.Duration(0) maxDuration = time.Duration(1<<63 - 1) ) var defaultBoundaries = newBoundaries([]time.Duration{ 10 * time.Microsecond, 100 * time.Microsecond, time.Millisecond, 10 * time.Millisecond, 100 * time.Millisecond, time.Second, 10 * time.Second, 100 * time.Second, }) // boundaries represents the interval bounds for the latency based samples. type boundaries struct { durations []time.Duration } // newBoundaries returns a new boundaries. func newBoundaries(durations []time.Duration) *boundaries { sort.Slice(durations, func(i, j int) bool { return durations[i] < durations[j] }) return &boundaries{durations: durations} } // numBuckets returns the number of buckets needed for these boundaries. func (lb boundaries) numBuckets() int { return len(lb.durations) + 1 } // getBucketIndex returns the appropriate bucket index for a given latency. func (lb boundaries) getBucketIndex(latency time.Duration) int { i := 0 for i < len(lb.durations) && latency >= lb.durations[i] { i++ } return i } open-telemetry-opentelemetry-go-contrib-e5abccb/zpages/boundaries_test.go000066400000000000000000000022171470323427300272650ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package zpages import ( "testing" "time" "github.com/stretchr/testify/assert" ) var testDurations = []time.Duration{1 * time.Second} func TestBoundariesNumBuckets(t *testing.T) { assert.Equal(t, 1, newBoundaries(nil).numBuckets()) assert.Equal(t, 1, newBoundaries([]time.Duration{}).numBuckets()) assert.Equal(t, 2, newBoundaries(testDurations).numBuckets()) assert.Equal(t, 9, defaultBoundaries.numBuckets()) } func TestBoundariesGetBucketIndex(t *testing.T) { assert.Equal(t, 0, newBoundaries(testDurations).getBucketIndex(zeroDuration)) assert.Equal(t, 0, newBoundaries(testDurations).getBucketIndex(500*time.Millisecond)) assert.Equal(t, 1, newBoundaries(testDurations).getBucketIndex(1500*time.Millisecond)) assert.Equal(t, 0, newBoundaries(testDurations).getBucketIndex(zeroDuration)) assert.Equal(t, 0, defaultBoundaries.getBucketIndex(zeroDuration)) assert.Equal(t, 3, defaultBoundaries.getBucketIndex(5*time.Millisecond)) assert.Equal(t, 6, defaultBoundaries.getBucketIndex(5*time.Second)) assert.Equal(t, 8, defaultBoundaries.getBucketIndex(maxDuration)) } open-telemetry-opentelemetry-go-contrib-e5abccb/zpages/bucket.go000066400000000000000000000044051470323427300253510ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Copyright 2017, OpenCensus Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package zpages // import "go.opentelemetry.io/contrib/zpages" import ( "time" sdktrace "go.opentelemetry.io/otel/sdk/trace" ) const ( // defaultBucketCapacity is the default capacity for every bucket (latency or error based). defaultBucketCapacity = 10 // samplePeriod is the minimum time between accepting spans in a single bucket. samplePeriod = time.Second ) // bucket is a container for a set of spans for latency buckets or errored spans. type bucket struct { nextTime time.Time // next time we can accept a span buffer []sdktrace.ReadOnlySpan // circular buffer of spans nextIndex int // location next ReadOnlySpan should be placed in buffer overflow bool // whether the circular buffer has wrapped around } // newBucket returns a new bucket with the given capacity. func newBucket(capacity uint) *bucket { return &bucket{ buffer: make([]sdktrace.ReadOnlySpan, capacity), } } // add adds a span to the bucket, if nextTime has been reached. func (b *bucket) add(s sdktrace.ReadOnlySpan) { if s.EndTime().Before(b.nextTime) { return } if len(b.buffer) == 0 { return } b.nextTime = s.EndTime().Add(samplePeriod) b.buffer[b.nextIndex] = s b.nextIndex++ if b.nextIndex == len(b.buffer) { b.nextIndex = 0 b.overflow = true } } // len returns the number of spans in the bucket. func (b *bucket) len() int { if b.overflow { return len(b.buffer) } return b.nextIndex } // spans returns the spans in this bucket. func (b *bucket) spans() []sdktrace.ReadOnlySpan { return append([]sdktrace.ReadOnlySpan(nil), b.buffer[0:b.len()]...) } open-telemetry-opentelemetry-go-contrib-e5abccb/zpages/bucket_test.go000066400000000000000000000043771470323427300264200ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package zpages import ( "testing" "time" "github.com/stretchr/testify/assert" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" ) type testSpan struct { sdktrace.ReadWriteSpan spanContext trace.SpanContext name string startTime time.Time endTime time.Time status sdktrace.Status } func (ts *testSpan) SpanContext() trace.SpanContext { return ts.spanContext } func (ts *testSpan) Status() sdktrace.Status { return ts.status } func (ts *testSpan) Name() string { return ts.name } func (ts *testSpan) StartTime() time.Time { return ts.startTime } func (ts *testSpan) EndTime() time.Time { return ts.endTime } func TestBucket(t *testing.T) { bkt := newBucket(defaultBucketCapacity) assert.Equal(t, 0, bkt.len()) for i := 1; i <= defaultBucketCapacity; i++ { bkt.add(&testSpan{endTime: time.Unix(int64(i), 0)}) assert.Equal(t, i, bkt.len()) spans := bkt.spans() assert.Len(t, spans, i) for j := 0; j < i; j++ { assert.Equal(t, time.Unix(int64(j+1), 0), spans[j].EndTime()) } } for i := defaultBucketCapacity + 1; i <= 2*defaultBucketCapacity; i++ { bkt.add(&testSpan{endTime: time.Unix(int64(i), 0)}) assert.Equal(t, defaultBucketCapacity, bkt.len()) spans := bkt.spans() assert.Len(t, spans, defaultBucketCapacity) // First spans will have newer times, and will replace older timestamps. for j := 0; j < i-defaultBucketCapacity; j++ { assert.Equal(t, time.Unix(int64(j+defaultBucketCapacity+1), 0), spans[j].EndTime()) } for j := i - defaultBucketCapacity; j < defaultBucketCapacity; j++ { assert.Equal(t, time.Unix(int64(j+1), 0), spans[j].EndTime()) } } } func TestBucketAddSample(t *testing.T) { bkt := newBucket(defaultBucketCapacity) assert.Equal(t, 0, bkt.len()) for i := 0; i < 1000; i++ { bkt.add(&testSpan{endTime: time.Unix(1, int64(i*1000))}) assert.Equal(t, 1, bkt.len()) spans := bkt.spans() assert.Len(t, spans, 1) assert.Equal(t, time.Unix(1, 0), spans[0].EndTime()) } } func TestBucketZeroCapacity(t *testing.T) { bkt := newBucket(0) assert.Equal(t, 0, bkt.len()) bkt.add(&testSpan{endTime: time.Unix(1, 0)}) assert.Equal(t, 0, bkt.len()) assert.Empty(t, bkt.spans()) } open-telemetry-opentelemetry-go-contrib-e5abccb/zpages/doc.go000066400000000000000000000003461470323427300246410ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package zpages implements a collection of HTML pages that display // telemetry stats. package zpages // import "go.opentelemetry.io/contrib/zpages" open-telemetry-opentelemetry-go-contrib-e5abccb/zpages/go.mod000066400000000000000000000011051470323427300246450ustar00rootroot00000000000000module go.opentelemetry.io/contrib/zpages go 1.22 require ( github.com/stretchr/testify v1.9.0 go.opentelemetry.io/otel v1.31.0 go.opentelemetry.io/otel/sdk v1.31.0 go.opentelemetry.io/otel/trace v1.31.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect golang.org/x/sys v0.26.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) open-telemetry-opentelemetry-go-contrib-e5abccb/zpages/go.sum000066400000000000000000000046721470323427300247060ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= open-telemetry-opentelemetry-go-contrib-e5abccb/zpages/internal/000077500000000000000000000000001470323427300253565ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/zpages/internal/gen.go000066400000000000000000000003271470323427300264600ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package internal // import "go.opentelemetry.io/contrib/zpages/internal" import "embed" //go:embed templates/* var Templates embed.FS open-telemetry-opentelemetry-go-contrib-e5abccb/zpages/internal/templates/000077500000000000000000000000001470323427300273545ustar00rootroot00000000000000open-telemetry-opentelemetry-go-contrib-e5abccb/zpages/internal/templates/footer.html000066400000000000000000000000201470323427300315300ustar00rootroot00000000000000 open-telemetry-opentelemetry-go-contrib-e5abccb/zpages/internal/templates/header.html000066400000000000000000000007371470323427300315010ustar00rootroot00000000000000 {{.Title}}

{{.Title}}

open-telemetry-opentelemetry-go-contrib-e5abccb/zpages/internal/templates/summary.html000066400000000000000000000031371470323427300317430ustar00rootroot00000000000000 {{range .LatencyBucketNames}}{{end}} {{$a := .TracesEndpoint}} {{$links := .Links}} {{range $rowindex, $row := .Rows}} {{- $name := .Name}} {{- if even $rowindex}}{{else}}{{end -}} {{- if $links -}} {{- else -}} {{- end -}} {{- if $links -}} {{range $index, $value := .Latency}}{{end}} {{- else -}} {{range .Latency}}{{end}} {{- end -}} {{- if $links -}} {{- else -}} {{- end -}} {{end}}
Span Name   |  Running   |   Latency Samples   |   Error Samples
  |     |  [{{.}}]  |  
{{.Name}}  |  {{.Active}}{{.Active}}  |  {{$value}}{{.}}  |  {{.Errors}}{{.Errors}}
open-telemetry-opentelemetry-go-contrib-e5abccb/zpages/internal/templates/traces.html000066400000000000000000000006441470323427300315270ustar00rootroot00000000000000

Span Name: {{.Name}}

{{.Num}} Requests

When                       Elapsed (sec)
----------------------------------------
{{range .Rows}}{{printf "%26s" (index .Fields 0)}} {{printf "%12s" (index .Fields 1)}} {{index .Fields 2}}{{.|spanRow}}
{{end}}

TraceId means sampled request. TraceId means not sampled request.

open-telemetry-opentelemetry-go-contrib-e5abccb/zpages/spanprocessor.go000066400000000000000000000144351470323427300270010ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Copyright 2017, OpenCensus Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package zpages // import "go.opentelemetry.io/contrib/zpages" import ( "context" "sync" "go.opentelemetry.io/otel/codes" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" ) var _ sdktrace.SpanProcessor = (*SpanProcessor)(nil) // perMethodSummary is a summary of the spans stored for a single span name. type perMethodSummary struct { activeSpans int latencySpans []int errorSpans int } // SpanProcessor is an sdktrace.SpanProcessor implementation that exposes zpages functionality for opentelemetry-go. // // It tracks all active spans, and stores samples of spans based on latency for non errored spans, // and samples for errored spans. type SpanProcessor struct { // Cannot keep track of the active Spans per name because the Span interface, // allows the name to be changed, and that will leak memory. activeSpansStore sync.Map spanSampleStores sync.Map } // NewSpanProcessor returns a new SpanProcessor. func NewSpanProcessor() *SpanProcessor { return &SpanProcessor{} } // OnStart adds span as active and reports it with zpages. func (ssm *SpanProcessor) OnStart(_ context.Context, span sdktrace.ReadWriteSpan) { sc := span.SpanContext() if sc.IsValid() { ssm.activeSpansStore.Store(spanKey(sc), span) } } // OnEnd processes all spans and reports them with zpages. func (ssm *SpanProcessor) OnEnd(span sdktrace.ReadOnlySpan) { sc := span.SpanContext() if sc.IsValid() { ssm.activeSpansStore.Delete(spanKey(sc)) } name := span.Name() value, ok := ssm.spanSampleStores.Load(name) if !ok { value, _ = ssm.spanSampleStores.LoadOrStore(name, newSampleStore(defaultBucketCapacity, defaultBucketCapacity)) } value.(*sampleStore).sampleSpan(span) } // Shutdown does nothing. func (ssm *SpanProcessor) Shutdown(context.Context) error { // Do nothing return nil } // ForceFlush does nothing. func (ssm *SpanProcessor) ForceFlush(context.Context) error { // Do nothing return nil } // spanStoreForName returns the sampleStore for the given name. // // It returns nil if it doesn't exist. func (ssm *SpanProcessor) spanStoreForName(name string) *sampleStore { if value, ok := ssm.spanSampleStores.Load(name); ok { return value.(*sampleStore) } return nil } // spansPerMethod returns a summary of what spans are being stored for each span name. func (ssm *SpanProcessor) spansPerMethod() map[string]*perMethodSummary { out := make(map[string]*perMethodSummary) ssm.spanSampleStores.Range(func(name, s interface{}) bool { out[name.(string)] = s.(*sampleStore).perMethodSummary() return true }) ssm.activeSpansStore.Range(func(_, sp interface{}) bool { span := sp.(sdktrace.ReadOnlySpan) if pms, ok := out[span.Name()]; ok { pms.activeSpans++ return true } out[span.Name()] = &perMethodSummary{activeSpans: 1} return true }) return out } // activeSpans returns the active spans for the given name. func (ssm *SpanProcessor) activeSpans(name string) []sdktrace.ReadOnlySpan { var out []sdktrace.ReadOnlySpan ssm.activeSpansStore.Range(func(_, sp interface{}) bool { span := sp.(sdktrace.ReadOnlySpan) if span.Name() == name { out = append(out, span) } return true }) return out } // errorSpans returns a sample of error spans. func (ssm *SpanProcessor) errorSpans(name string) []sdktrace.ReadOnlySpan { s := ssm.spanStoreForName(name) if s == nil { return nil } return s.errorSpans() } // spansByLatency returns a sample of successful spans. // // minLatency is the minimum latency of spans to be returned. // maxDuration, if nonzero, is the maximum latency of spans to be returned. func (ssm *SpanProcessor) spansByLatency(name string, latencyBucketIndex int) []sdktrace.ReadOnlySpan { s := ssm.spanStoreForName(name) if s == nil { return nil } return s.spansByLatency(latencyBucketIndex) } // sampleStore stores a sampled of spans for a particular span name. // // It contains sample of spans for error requests (status code is codes.Error); // and a sample of spans for successful requests, bucketed by latency. type sampleStore struct { sync.Mutex // protects everything below. latency []*bucket errors *bucket } // newSampleStore creates a sampleStore. func newSampleStore(latencyBucketSize uint, errorBucketSize uint) *sampleStore { s := &sampleStore{ latency: make([]*bucket, defaultBoundaries.numBuckets()), errors: newBucket(errorBucketSize), } for i := range s.latency { s.latency[i] = newBucket(latencyBucketSize) } return s } func (ss *sampleStore) perMethodSummary() *perMethodSummary { ss.Lock() defer ss.Unlock() p := &perMethodSummary{} p.errorSpans = ss.errors.len() for _, b := range ss.latency { p.latencySpans = append(p.latencySpans, b.len()) } return p } func (ss *sampleStore) spansByLatency(latencyBucketIndex int) []sdktrace.ReadOnlySpan { ss.Lock() defer ss.Unlock() if latencyBucketIndex < 0 || latencyBucketIndex >= len(ss.latency) { return nil } return ss.latency[latencyBucketIndex].spans() } func (ss *sampleStore) errorSpans() []sdktrace.ReadOnlySpan { ss.Lock() defer ss.Unlock() return ss.errors.spans() } // sampleSpan removes adds to the corresponding latency or error bucket. func (ss *sampleStore) sampleSpan(span sdktrace.ReadOnlySpan) { code := span.Status().Code ss.Lock() defer ss.Unlock() if code == codes.Error { ss.errors.add(span) return } latency := span.EndTime().Sub(span.StartTime()) // In case of time skew or wrong time, sample as 0 latency. if latency < 0 { latency = 0 } ss.latency[defaultBoundaries.getBucketIndex(latency)].add(span) } func spanKey(sc trace.SpanContext) [24]byte { var sk [24]byte tid := sc.TraceID() copy(sk[0:16], tid[:]) sid := sc.SpanID() copy(sk[16:24], sid[:]) return sk } open-telemetry-opentelemetry-go-contrib-e5abccb/zpages/spanprocessor_test.go000066400000000000000000000144751470323427300300440ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package zpages import ( "context" "reflect" "sort" "sync" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/codes" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" ) func TestSpanProcessorDoNothing(t *testing.T) { zsp := NewSpanProcessor() assert.NoError(t, zsp.ForceFlush(context.Background())) assert.NoError(t, zsp.Shutdown(context.Background())) } func TestSpanProcessor(t *testing.T) { zsp := NewSpanProcessor() tracerProvider := sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithSpanProcessor(zsp), ) const spanName = "testSpan" const numSpans = 9 tracer := tracerProvider.Tracer("test") spans := createActiveSpans(tracer, spanName, numSpans) // Sort the spans by the address pointer so we can compare. sort.Slice(spans, func(i, j int) bool { return reflect.ValueOf(spans[i]).Pointer() < reflect.ValueOf(spans[j]).Pointer() }) require.Len(t, spans, numSpans) activeSpans := zsp.activeSpans(spanName) assert.Len(t, activeSpans, numSpans) // Sort the activeSpans by the address pointer so we can compare. sort.Slice(activeSpans, func(i, j int) bool { return reflect.ValueOf(activeSpans[i]).Pointer() < reflect.ValueOf(activeSpans[j]).Pointer() }) for i := range spans { assert.Same(t, spans[i], activeSpans[i]) } // No ended spans so there will be no error, no latency samples. assert.Empty(t, zsp.errorSpans(spanName)) for i := 0; i < defaultBoundaries.numBuckets(); i++ { assert.Empty(t, zsp.spansByLatency(spanName, i)) } spansPM := zsp.spansPerMethod() require.Len(t, spansPM, 1) assert.Equal(t, numSpans, spansPM[spanName].activeSpans) // End all Spans, they will end pretty fast, so we can only check that we have at least one in // errors and one in latency samples. for _, s := range spans { s.End() } // Test that no more active spans. assert.Empty(t, zsp.activeSpans(spanName)) assert.LessOrEqual(t, 1, len(zsp.errorSpans(spanName))) numLatencySamples := 0 for i := 0; i < defaultBoundaries.numBuckets(); i++ { numLatencySamples += len(zsp.spansByLatency(spanName, i)) } assert.LessOrEqual(t, 1, numLatencySamples) } func TestSpanProcessorFuzzer(t *testing.T) { zsp := NewSpanProcessor() tracerProvider := sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithSpanProcessor(zsp), ) const numIterations = 200 const numSpansPerIteration = 90 var wg sync.WaitGroup wg.Add(4) go func() { for i := 0; i < numIterations; i++ { assert.LessOrEqual(t, 0, len(zsp.spansPerMethod())) assert.GreaterOrEqual(t, 2, len(zsp.spansPerMethod())) createEndedSpans(tracerProvider.Tracer("test1"), "testSpan1", numSpansPerIteration) // Call for spans names created by the other goroutines. assert.LessOrEqual(t, 0, len(zsp.activeSpans("testSpan2"))) assert.LessOrEqual(t, 0, len(zsp.errorSpans("testSpan2"))) assert.LessOrEqual(t, 0, len(zsp.spansByLatency("testSpan2", 1))) } wg.Done() }() go func() { for i := 0; i < numIterations; i++ { assert.LessOrEqual(t, 0, len(zsp.spansPerMethod())) assert.GreaterOrEqual(t, 2, len(zsp.spansPerMethod())) createEndedSpans(tracerProvider.Tracer("test2"), "testSpan2", numSpansPerIteration) // Call for spans names created by the other goroutines. assert.LessOrEqual(t, 0, len(zsp.activeSpans("testSpan1"))) assert.LessOrEqual(t, 0, len(zsp.errorSpans("testSpan1"))) assert.LessOrEqual(t, 0, len(zsp.spansByLatency("testSpan1", 1))) } wg.Done() }() go func() { for i := 0; i < numIterations; i++ { assert.LessOrEqual(t, 0, len(zsp.spansPerMethod())) assert.GreaterOrEqual(t, 2, len(zsp.spansPerMethod())) createEndedSpans(tracerProvider.Tracer("test3"), "testSpan1", numSpansPerIteration) // Call for spans names created by the other goroutines. assert.LessOrEqual(t, 0, len(zsp.activeSpans("testSpan2"))) assert.LessOrEqual(t, 0, len(zsp.errorSpans("testSpan2"))) assert.LessOrEqual(t, 0, len(zsp.spansByLatency("testSpan2", 1))) } wg.Done() }() go func() { for i := 0; i < numIterations; i++ { assert.LessOrEqual(t, 0, len(zsp.spansPerMethod())) assert.GreaterOrEqual(t, 2, len(zsp.spansPerMethod())) createEndedSpans(tracerProvider.Tracer("test4"), "testSpan2", numSpansPerIteration) // Call for spans names created by the other goroutines. assert.LessOrEqual(t, 0, len(zsp.activeSpans("testSpan1"))) assert.LessOrEqual(t, 0, len(zsp.errorSpans("testSpan1"))) assert.LessOrEqual(t, 0, len(zsp.spansByLatency("testSpan1", 1))) } wg.Done() }() wg.Wait() } func TestSpanProcessorNegativeLatency(t *testing.T) { zsp := NewSpanProcessor() ts := &testSpan{ spanContext: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1}, SpanID: [8]byte{1, 2, 3, 4, 5, 6, 7, 8}, TraceFlags: 1, Remote: false, }), name: "test", startTime: time.Unix(10, 0), endTime: time.Unix(5, 0), status: sdktrace.Status{ Code: codes.Ok, Description: "", }, } zsp.OnStart(context.Background(), ts) spansPM := zsp.spansPerMethod() require.Len(t, spansPM, 1) assert.Equal(t, 1, spansPM["test"].activeSpans) zsp.OnEnd(ts) spansPM = zsp.spansPerMethod() require.Len(t, spansPM, 1) assert.Equal(t, 1, spansPM["test"].latencySpans[0]) } func TestSpanProcessorSpansByLatencyWrongIndex(t *testing.T) { zsp := NewSpanProcessor() tracerProvider := sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithSpanProcessor(zsp), ) tracer := tracerProvider.Tracer("test") createEndedSpans(tracer, "test", 6) assert.Nil(t, zsp.spansByLatency("test", -1)) assert.Nil(t, zsp.spansByLatency("test", defaultBoundaries.numBuckets())) } func createEndedSpans(tracer trace.Tracer, spanName string, numSpans int) { for i := 0; i < numSpans; i++ { _, span := tracer.Start(context.Background(), spanName) span.SetStatus(codes.Code(i%3), "") span.End() } } func createActiveSpans(tracer trace.Tracer, spanName string, numSpans int) []trace.Span { var spans []trace.Span for i := 0; i < numSpans; i++ { _, span := tracer.Start(context.Background(), spanName) span.SetStatus(codes.Code(i%3), "") spans = append(spans, span) } return spans } open-telemetry-opentelemetry-go-contrib-e5abccb/zpages/templates.go000066400000000000000000000046521470323427300260760ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Copyright 2017, OpenCensus Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // package zpages // import "go.opentelemetry.io/contrib/zpages" import ( "fmt" "html/template" "io" "log" "go.opentelemetry.io/contrib/zpages/internal" ) var ( templateFunctions = template.FuncMap{ "even": even, "spanRow": spanRowFormatter, } headerTemplate = parseTemplate("header") summaryTableTemplate = parseTemplate("summary") tracesTableTemplate = parseTemplate("traces") footerTemplate = parseTemplate("footer") ) // headerData contains data for the header template. type headerData struct { Title string } func parseTemplate(name string) *template.Template { f, err := internal.Templates.Open("templates/" + name + ".html") if err != nil { log.Panicf("%v: %v", name, err) // nolint: revive // Called during initialization. } defer func() { if err = f.Close(); err != nil { log.Panicf("%v: %v", name, err) // nolint: revive // Called during initialization. } }() text, err := io.ReadAll(f) if err != nil { log.Panicf("%v: %v", name, err) // nolint: revive // Called during initialization. } return template.Must(template.New(name).Funcs(templateFunctions).Parse(string(text))) } func spanRowFormatter(r spanRow) template.HTML { if !r.SpanContext.IsValid() { return "" } col := "black" if r.SpanContext.IsSampled() { col = "blue" } tpl := fmt.Sprintf( `trace_id: %s span_id: %s`, col, r.SpanContext.TraceID(), r.SpanContext.SpanID(), ) if r.ParentSpanContext.IsValid() { tpl += fmt.Sprintf(` parent_span_id: %s`, r.ParentSpanContext.SpanID()) } //nolint:gosec // G203: None of the dynamic attributes (TraceID/SpanID) can // contain characters that need escaping so this lint issue is a false // positive. return template.HTML(tpl) } func even(x int) bool { return x%2 == 0 } open-telemetry-opentelemetry-go-contrib-e5abccb/zpages/templates_test.go000066400000000000000000000032401470323427300271250ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package zpages import ( "html/template" "testing" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/trace" ) func TestSpanRowFormatter(t *testing.T) { for _, tt := range []struct { name string row spanRow expectedTemplate template.HTML }{ { name: "with an invalid span context", row: spanRow{ SpanContext: trace.NewSpanContext(trace.SpanContextConfig{}), }, expectedTemplate: "", }, { name: "with a valid span context", row: spanRow{ SpanContext: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: trace.TraceID{2, 3, 4, 5, 6, 7, 8, 9, 2, 3, 4, 5, 6, 7, 8, 9}, SpanID: trace.SpanID{1, 2, 3, 4, 5, 6, 7, 8}, }), }, expectedTemplate: "trace_id: 02030405060708090203040506070809 span_id: 0102030405060708", }, { name: "with a valid parent span context", row: spanRow{ SpanContext: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: trace.TraceID{2, 3, 4, 5, 6, 7, 8, 9, 2, 3, 4, 5, 6, 7, 8, 9}, SpanID: trace.SpanID{1, 2, 3, 4, 5, 6, 7, 8}, }), ParentSpanContext: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: trace.TraceID{10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25}, SpanID: trace.SpanID{10, 11, 12, 13, 14, 15, 16, 18}, }), }, expectedTemplate: "trace_id: 02030405060708090203040506070809 span_id: 0102030405060708 parent_span_id: 0a0b0c0d0e0f1012", }, } { t.Run(tt.name, func(t *testing.T) { r := spanRowFormatter(tt.row) assert.Equal(t, tt.expectedTemplate, r) }) } } open-telemetry-opentelemetry-go-contrib-e5abccb/zpages/tracez.go000066400000000000000000000166451470323427300253750ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Copyright 2017, OpenCensus Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package zpages // import "go.opentelemetry.io/contrib/zpages" import ( "fmt" "log" "net/http" "sort" "strconv" "strings" "time" "go.opentelemetry.io/otel/attribute" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" ) const ( // spanNameQueryField is the header for span name. spanNameQueryField = "zspanname" // spanTypeQueryField is the header for type (running = 0, latency = 1, error = 2) to display. spanTypeQueryField = "ztype" // spanLatencyBucketQueryField is the header for latency based samples. // Default is [0, 8] representing the latency buckets, where 0 is the first one. spanLatencyBucketQueryField = "zlatencybucket" // maxTraceMessageLength is the maximum length of a message in tracez output. maxTraceMessageLength = 1024 ) type summaryTableData struct { Header []string LatencyBucketNames []string Links bool TracesEndpoint string Rows []summaryTableRowData } type summaryTableRowData struct { Name string Active int Latency []int Errors int } // traceTableData contains data for the trace data template. type traceTableData struct { Name string Num int Rows []spanRow } var _ http.Handler = (*tracezHandler)(nil) type tracezHandler struct { sp *SpanProcessor } // NewTracezHandler returns an http.Handler that can be used to serve HTTP requests for trace zpages. func NewTracezHandler(sp *SpanProcessor) http.Handler { return &tracezHandler{sp: sp} } // ServeHTTP implements the http.Handler and is capable of serving "tracez" HTTP requests. func (th *tracezHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html; charset=utf-8") if err := r.ParseForm(); err != nil { w.WriteHeader(http.StatusBadRequest) return } spanName := r.Form.Get(spanNameQueryField) spanType, _ := strconv.Atoi(r.Form.Get(spanTypeQueryField)) spanSubtype, _ := strconv.Atoi(r.Form.Get(spanLatencyBucketQueryField)) if err := headerTemplate.Execute(w, headerData{Title: "Trace Spans"}); err != nil { log.Printf("zpages: executing template: %v", err) } if err := summaryTableTemplate.Execute(w, th.getSummaryTableData()); err != nil { log.Printf("zpages: executing template: %v", err) } if spanName != "" { if err := tracesTableTemplate.Execute(w, th.getTraceTableData(spanName, spanType, spanSubtype)); err != nil { log.Printf("zpages: executing template: %v", err) } } if err := footerTemplate.Execute(w, nil); err != nil { log.Printf("zpages: executing template: %v", err) } } func (th *tracezHandler) getTraceTableData(spanName string, spanType, latencyBucket int) traceTableData { var spans []sdktrace.ReadOnlySpan switch spanType { case 0: // active spans = th.sp.activeSpans(spanName) case 1: // latency spans = th.sp.spansByLatency(spanName, latencyBucket) case 2: // error spans = th.sp.errorSpans(spanName) } data := traceTableData{ Name: spanName, Num: len(spans), } for _, s := range spans { data.Rows = append(data.Rows, spanRows(s)...) } return data } func (th *tracezHandler) getSummaryTableData() summaryTableData { data := summaryTableData{ Links: true, TracesEndpoint: "tracez", } data.Header = []string{"Name", "active"} // An implicit 0 lower bound latency bucket is always present. latencyBuckets := append([]time.Duration{0}, defaultBoundaries.durations...) for _, l := range latencyBuckets { s := fmt.Sprintf(">%v", l) data.Header = append(data.Header, s) data.LatencyBucketNames = append(data.LatencyBucketNames, s) } data.Header = append(data.Header, "Errors") for name, s := range th.sp.spansPerMethod() { row := summaryTableRowData{Name: name, Active: s.activeSpans, Errors: s.errorSpans, Latency: s.latencySpans} data.Rows = append(data.Rows, row) } sort.Slice(data.Rows, func(i, j int) bool { return data.Rows[i].Name < data.Rows[j].Name }) return data } type spanRow struct { Fields [3]string trace.SpanContext ParentSpanContext trace.SpanContext } type events []sdktrace.Event func (e events) Len() int { return len(e) } func (e events) Less(i, j int) bool { return e[i].Time.Before(e[j].Time) } func (e events) Swap(i, j int) { e[i], e[j] = e[j], e[i] } type attributes []attribute.KeyValue func (e attributes) Len() int { return len(e) } func (e attributes) Less(i, j int) bool { return string(e[i].Key) < string(e[j].Key) } func (e attributes) Swap(i, j int) { e[i], e[j] = e[j], e[i] } func spanRows(s sdktrace.ReadOnlySpan) []spanRow { start := s.StartTime() lasty, lastm, lastd := start.Date() wholeTime := func(t time.Time) string { return t.Format("2006/01/02-15:04:05") + fmt.Sprintf(".%06d", t.Nanosecond()/1000) } formatTime := func(t time.Time) string { y, m, d := t.Date() if y == lasty && m == lastm && d == lastd { return t.Format(" 15:04:05") + fmt.Sprintf(".%06d", t.Nanosecond()/1000) } lasty, lastm, lastd = y, m, d return wholeTime(t) } lastTime := start formatElapsed := func(t time.Time) string { d := t.Sub(lastTime) lastTime = t u := int64(d / 1000) // There are five cases for duration printing: // -1234567890s // -1234.123456 // .123456 // 12345.123456 // 12345678901s switch { case u < -9999999999: return fmt.Sprintf("%11ds", u/1e6) case u < 0: sec := u / 1e6 u -= sec * 1e6 return fmt.Sprintf("%5d.%06d", sec, -u) case u < 1e6: return fmt.Sprintf(" .%6d", u) case u <= 99999999999: sec := u / 1e6 u -= sec * 1e6 return fmt.Sprintf("%5d.%06d", sec, u) default: return fmt.Sprintf("%11ds", u/1e6) } } firstRow := spanRow{Fields: [3]string{wholeTime(start), "", ""}, SpanContext: s.SpanContext(), ParentSpanContext: s.Parent()} if s.EndTime().IsZero() { firstRow.Fields[1] = " " } else { firstRow.Fields[1] = formatElapsed(s.EndTime()) lastTime = start } out := []spanRow{firstRow} formatAttributes := func(a attributes) string { sort.Sort(a) var s []string for i := range a { s = append(s, fmt.Sprintf("%s=%v", a[i].Key, a[i].Value.Emit())) } return "Attributes:{" + strings.Join(s, ", ") + "}" } msg := fmt.Sprintf("Status{Code=%s, description=%q}", s.Status().Code.String(), s.Status().Description) out = append(out, spanRow{Fields: [3]string{"", "", msg}}) if len(s.Attributes()) != 0 { out = append(out, spanRow{Fields: [3]string{"", "", formatAttributes(s.Attributes())}}) } es := events(s.Events()) sort.Sort(es) for _, e := range es { msg := e.Name if len(e.Attributes) != 0 { msg = msg + " " + formatAttributes(e.Attributes) } row := spanRow{Fields: [3]string{ formatTime(e.Time), formatElapsed(e.Time), msg, }} out = append(out, row) } for i := range out { if len(out[i].Fields[2]) > maxTraceMessageLength { out[i].Fields[2] = out[i].Fields[2][:maxTraceMessageLength] } } return out } open-telemetry-opentelemetry-go-contrib-e5abccb/zpages/version.go000066400000000000000000000007601470323427300255610ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package zpages // import "go.opentelemetry.io/contrib/zpages" // Version is the current release version of the zpages span processor. func Version() string { return "0.56.0" // This string is updated by the pre_release.sh script during release } // SemVersion is the semantic version to be supplied to tracer/meter creation. // // Deprecated: Use [Version] instead. func SemVersion() string { return Version() }