From c02d9fdddcd4918fe1190f0ae73e697112fdfbb6 Mon Sep 17 00:00:00 2001 From: "Han Verstraete (OpenFaaS Ltd)" Date: Mon, 2 Jun 2025 13:21:06 +0200 Subject: [PATCH] Add golang-otel template Signed-off-by: Han Verstraete (OpenFaaS Ltd) --- README.md | 12 ++ template/golang-otel/.gitignore | 1 + template/golang-otel/Dockerfile | 60 +++++++ template/golang-otel/function/go.mod | 3 + template/golang-otel/function/handler.go | 22 +++ template/golang-otel/go.mod | 32 ++++ template/golang-otel/go.sum | 63 +++++++ template/golang-otel/go.work | 6 + template/golang-otel/go.work.sum | 29 +++ template/golang-otel/main.go | 214 +++++++++++++++++++++++ template/golang-otel/template.yml | 11 ++ 11 files changed, 453 insertions(+) create mode 100644 template/golang-otel/.gitignore create mode 100644 template/golang-otel/Dockerfile create mode 100644 template/golang-otel/function/go.mod create mode 100644 template/golang-otel/function/handler.go create mode 100644 template/golang-otel/go.mod create mode 100644 template/golang-otel/go.sum create mode 100644 template/golang-otel/go.work create mode 100644 template/golang-otel/go.work.sum create mode 100644 template/golang-otel/main.go create mode 100644 template/golang-otel/template.yml diff --git a/README.md b/README.md index dd9e025..fdf6717 100644 --- a/README.md +++ b/README.md @@ -245,6 +245,18 @@ func Handle(w http.ResponseWriter, r *http.Request) { } ``` +### OpenTelemetry + +The `golang-otel` template as a variant of the `golang-middleware` template with built-in support for [OpenTelemetry](https://opentelemetry.io/) traces. It allow you to collect traces for HTTP requests by default and makes it easy to extend the function with your own spans. + +Create a new function with OpenTelemetry support by running: + +```sh +faas new --lang golang-otel +``` + +See the [OpenFaaS docs](https://docs.openfaas.com/languages/go/#opentelemetry-instrumentation) for information on how to use the template. + ## 2.0 golang-http This template provides additional context and control over the HTTP response from your function. diff --git a/template/golang-otel/.gitignore b/template/golang-otel/.gitignore new file mode 100644 index 0000000..8915b60 --- /dev/null +++ b/template/golang-otel/.gitignore @@ -0,0 +1 @@ +/handler diff --git a/template/golang-otel/Dockerfile b/template/golang-otel/Dockerfile new file mode 100644 index 0000000..547b762 --- /dev/null +++ b/template/golang-otel/Dockerfile @@ -0,0 +1,60 @@ +FROM --platform=${TARGETPLATFORM:-linux/amd64} ghcr.io/openfaas/of-watchdog:0.10.9 AS watchdog +FROM --platform=${BUILDPLATFORM:-linux/amd64} golang:1.23-alpine AS build + +ARG TARGETPLATFORM +ARG BUILDPLATFORM +ARG TARGETOS +ARG TARGETARCH + +RUN apk --no-cache add git + +COPY --from=watchdog /fwatchdog /usr/bin/fwatchdog +RUN chmod +x /usr/bin/fwatchdog + +RUN mkdir -p /go/src/handler +WORKDIR /go/src/handler +COPY . . + +ARG GO111MODULE="on" +ARG GOPROXY="" +ARG GOFLAGS="" +ARG CGO_ENABLED=0 +ENV CGO_ENABLED=${CGO_ENABLED} + +# Run a gofmt and exclude all vendored code. +RUN test -z "$(gofmt -l $(find . -type f -name '*.go' -not -path "./vendor/*" -not -path "./function/vendor/*"))" || { echo "Run \"gofmt -s -w\" on your Golang code"; exit 1; } + +WORKDIR /go/src/handler/function +RUN mkdir -p /go/src/handler/function/static + +RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} go test ./... -cover + +WORKDIR /go/src/handler +RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} \ + go build --ldflags "-s -w" -o handler . + +FROM --platform=${TARGETPLATFORM:-linux/amd64} alpine:3.21.3 AS ship + +# Add non root user and certs +RUN apk --no-cache add ca-certificates \ + && addgroup -S app && adduser -S -g app app + +# Split instructions so that buildkit can run & cache +# the previous command ahead of time. +RUN mkdir -p /home/app \ + && chown app /home/app + +WORKDIR /home/app + +COPY --from=build --chown=app /go/src/handler/handler . +COPY --from=build --chown=app /usr/bin/fwatchdog . +COPY --from=build --chown=app /go/src/handler/function/static static + +USER app + +ENV fprocess="./handler" +ENV mode="http" +ENV upstream_url="http://127.0.0.1:8082" +ENV prefix_logs="false" + +CMD ["./fwatchdog"] diff --git a/template/golang-otel/function/go.mod b/template/golang-otel/function/go.mod new file mode 100644 index 0000000..45f94ac --- /dev/null +++ b/template/golang-otel/function/go.mod @@ -0,0 +1,3 @@ +module handler/function + +go 1.18 diff --git a/template/golang-otel/function/handler.go b/template/golang-otel/function/handler.go new file mode 100644 index 0000000..7fdcc9d --- /dev/null +++ b/template/golang-otel/function/handler.go @@ -0,0 +1,22 @@ +package function + +import ( + "fmt" + "io" + "net/http" +) + +func Handle(w http.ResponseWriter, r *http.Request) { + var input []byte + + if r.Body != nil { + defer r.Body.Close() + + body, _ := io.ReadAll(r.Body) + + input = body + } + + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, "Body: %s", input) +} diff --git a/template/golang-otel/go.mod b/template/golang-otel/go.mod new file mode 100644 index 0000000..99405bd --- /dev/null +++ b/template/golang-otel/go.mod @@ -0,0 +1,32 @@ +module handler + +go 1.23.0 + +require ( + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 + go.opentelemetry.io/otel v1.36.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.36.0 + go.opentelemetry.io/otel/sdk v1.36.0 + google.golang.org/grpc v1.72.1 +) + +require ( + github.com/cenkalti/backoff/v5 v5.0.2 // 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/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 // indirect + go.opentelemetry.io/otel/metric v1.36.0 // indirect + go.opentelemetry.io/otel/trace v1.36.0 // indirect + go.opentelemetry.io/proto/otlp v1.6.0 // indirect + golang.org/x/net v0.40.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/text v0.25.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect + google.golang.org/protobuf v1.36.6 // indirect +) diff --git a/template/golang-otel/go.sum b/template/golang-otel/go.sum new file mode 100644 index 0000000..cc08b33 --- /dev/null +++ b/template/golang-otel/go.sum @@ -0,0 +1,63 @@ +github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8= +github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= +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/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.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +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.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +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.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= +go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= +go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 h1:dNzwXjZKpMpE2JhmO+9HsPl42NIXFIFSUSSs0fiqra0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0/go.mod h1:90PoxvaEB5n6AOdZvi+yWJQoE95U8Dhhw2bSyRqnTD0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 h1:JgtbA0xkWHnTmYk7YusopJFX6uleBmAuZ8n05NEh8nQ= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0/go.mod h1:179AK5aar5R3eS9FucPy6rggvU0g52cvKId8pv4+v0c= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.36.0 h1:G8Xec/SgZQricwWBJF/mHZc7A02YHedfFDENwJEdRA0= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.36.0/go.mod h1:PD57idA/AiFD5aqoxGxCvT/ILJPeHy3MjqU/NS7KogY= +go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= +go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= +go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= +go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= +go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= +go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= +go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= +go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= +go.opentelemetry.io/proto/otlp v1.6.0 h1:jQjP+AQyTf+Fe7OKj/MfkDrmK4MNVtw2NpXsf9fefDI= +go.opentelemetry.io/proto/otlp v1.6.0/go.mod h1:cicgGehlFuNdgZkcALOCh3VE6K/u2tAjzlRhDwmVpZc= +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.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 h1:Kog3KlB4xevJlAcbbbzPfRG0+X9fdoGM+UBRKVz6Wr0= +google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237/go.mod h1:ezi0AVyMKDWy5xAncvjLWH7UcLBB5n7y2fQ8MzjJcto= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 h1:cJfm9zPbe1e873mHJzmQ1nwVEeRDU/T1wXDK2kUSU34= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA= +google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/template/golang-otel/go.work b/template/golang-otel/go.work new file mode 100644 index 0000000..b54f38b --- /dev/null +++ b/template/golang-otel/go.work @@ -0,0 +1,6 @@ +go 1.23.0 + +use ( + . + ./function +) diff --git a/template/golang-otel/go.work.sum b/template/golang-otel/go.work.sum new file mode 100644 index 0000000..4c3cb1a --- /dev/null +++ b/template/golang-otel/go.work.sum @@ -0,0 +1,29 @@ +cel.dev/expr v0.20.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= +cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.26.0/go.mod h1:2bIszWvQRlJVmJLiuLhukLImRjKPcYdzzsx6darK02A= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA= +github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw= +github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= +github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= +github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc= +github.com/golang/glog v1.2.4/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g= +github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= +go.opentelemetry.io/contrib/detectors/gcp v1.34.0/go.mod h1:cV4BMFcscUR/ckqLkbfQmF0PRsq8w/lMGzdbCSveBHo= +go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= +golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/template/golang-otel/main.go b/template/golang-otel/main.go new file mode 100644 index 0000000..8799004 --- /dev/null +++ b/template/golang-otel/main.go @@ -0,0 +1,214 @@ +package main + +import ( + "context" + "fmt" + "log" + "net/http" + "os" + "os/signal" + "strconv" + "strings" + "sync/atomic" + "syscall" + "time" + + "handler/function" + + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" + "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" + "go.opentelemetry.io/otel/sdk/resource" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + semconv "go.opentelemetry.io/otel/semconv/v1.26.0" + "google.golang.org/grpc" +) + +var ( + acceptingConnections int32 +) + +const defaultTimeout = 10 * time.Second + +func getExporters(ctx context.Context) ([]sdktrace.SpanExporter, error) { + var exporters []sdktrace.SpanExporter + + exporterTypes := os.Getenv("OTEL_TRACES_EXPORTER") + if exporterTypes == "" { + exporterTypes = "stdout" + } + + insecureValue := os.Getenv("OTEL_EXPORTER_OTLP_TRACES_INSECURE") + insecure, err := strconv.ParseBool(insecureValue) + if err != nil { + insecure = false + } + + for _, exp := range strings.Split(exporterTypes, ",") { + switch exp { + case "console": + exporter, err := stdouttrace.New(stdouttrace.WithPrettyPrint()) + if err != nil { + return nil, err + } + + exporters = append(exporters, exporter) + case "otlp": + endpoint := os.Getenv("OTEL_EXPORTER_OTLP_ENDPOINT") + if endpoint == "" { + endpoint = "0.0.0.0:4317" + } + + opts := []otlptracegrpc.Option{ + otlptracegrpc.WithEndpoint(endpoint), + otlptracegrpc.WithDialOption(grpc.WithBlock()), + } + + if insecure { + opts = append(opts, otlptracegrpc.WithInsecure()) + + } + + exporter, err := otlptracegrpc.New(ctx, opts...) + if err != nil { + return nil, err + } + + exporters = append(exporters, exporter) + default: + fmt.Printf("unknown OTEL exporter type: %s", exp) + } + } + + return exporters, nil +} + +func newTracerProvider(exporters []sdktrace.SpanExporter) *sdktrace.TracerProvider { + serviceName := os.Getenv("OTEL_SERVICE_NAME") + if serviceName == "" { + if functionName, ok := os.LookupEnv("OPENFAAS_NAME"); ok { + serviceName = functionName + + // If we are running in a kubernetes cluster, use the namespace in the service name + namespace := "" + data, err := os.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace") + if err == nil { + namespace = string(data) + } + + if len(namespace) > 0 { + serviceName = fmt.Sprintf("%s.%s", serviceName, namespace) + } + } + } + + if serviceName == "" { + serviceName = "unknown-function" + } + + // Ensure default SDK resources and the required service name are set. + r, err := resource.Merge( + resource.Default(), + resource.NewWithAttributes( + semconv.SchemaURL, + semconv.ServiceName(serviceName), + ), + ) + + if err != nil { + panic(err) + } + + opts := []sdktrace.TracerProviderOption{ + sdktrace.WithResource(r), + } + + for _, exporter := range exporters { + processor := sdktrace.NewBatchSpanProcessor(exporter) + opts = append(opts, sdktrace.WithSpanProcessor(processor)) + } + + return sdktrace.NewTracerProvider(opts...) +} + +func main() { + ctx := context.Background() + exporters, err := getExporters(ctx) + if err != nil { + log.Fatalf("failed to initialize exporters: %v", err) + } + + // Create a new tracer provider with a batch span processor and the given exporters. + tp := newTracerProvider(exporters) + otel.SetTracerProvider(tp) + + // Handle shutdown properly so nothing leaks. + defer func() { _ = tp.Shutdown(ctx) }() + + readTimeout := parseIntOrDurationValue(os.Getenv("read_timeout"), defaultTimeout) + writeTimeout := parseIntOrDurationValue(os.Getenv("write_timeout"), defaultTimeout) + healthInterval := parseIntOrDurationValue(os.Getenv("healthcheck_interval"), writeTimeout) + + s := &http.Server{ + Addr: fmt.Sprintf(":%d", 8082), + ReadTimeout: readTimeout, + WriteTimeout: writeTimeout, + MaxHeaderBytes: 1 << 20, // Max header of 1MB + } + + http.Handle("/", otelhttp.NewHandler(http.HandlerFunc(function.Handle), "Invoke")) + + listenUntilShutdown(s, healthInterval, writeTimeout) +} + +func listenUntilShutdown(s *http.Server, shutdownTimeout time.Duration, writeTimeout time.Duration) { + idleConnsClosed := make(chan struct{}) + go func() { + sig := make(chan os.Signal, 1) + signal.Notify(sig, syscall.SIGTERM) + + <-sig + + log.Printf("[entrypoint] SIGTERM: no connections in: %s", shutdownTimeout.String()) + <-time.Tick(shutdownTimeout) + + ctx, cancel := context.WithTimeout(context.Background(), writeTimeout) + defer cancel() + + if err := s.Shutdown(ctx); err != nil { + log.Printf("[entrypoint] Error in Shutdown: %v", err) + } + + log.Printf("[entrypoint] Exiting.") + + close(idleConnsClosed) + }() + + // Run the HTTP server in a separate go-routine. + go func() { + if err := s.ListenAndServe(); err != http.ErrServerClosed { + log.Printf("[entrypoint] Error ListenAndServe: %v", err) + close(idleConnsClosed) + } + }() + + atomic.StoreInt32(&acceptingConnections, 1) + + <-idleConnsClosed +} + +func parseIntOrDurationValue(val string, fallback time.Duration) time.Duration { + if len(val) > 0 { + parsedVal, parseErr := strconv.Atoi(val) + if parseErr == nil && parsedVal >= 0 { + return time.Duration(parsedVal) * time.Second + } + } + + duration, durationErr := time.ParseDuration(val) + if durationErr != nil { + return fallback + } + return duration +} diff --git a/template/golang-otel/template.yml b/template/golang-otel/template.yml new file mode 100644 index 0000000..ecf6afd --- /dev/null +++ b/template/golang-otel/template.yml @@ -0,0 +1,11 @@ +language: golang-otel +fprocess: ./handler +welcome_message: | + You have created a new function which uses go 1.23 with support for OpenTelemetry + instrumentation and Alpine Linux as its base image. + + To disable the go module, for private vendor code, please use + "--build-arg GO111MODULE=off" with faas-cli build or configure this + via your stack.yml file. + + Learn more: https://docs.openfaas.com/languages/go/