c738542201
This PR adds opentelemetry and chi wrapper to have basic instrumentation <!--start release-notes-assistant--> ## Draft release notes <!--URL:https://codeberg.org/forgejo/forgejo--> - Features - [PR](https://codeberg.org/forgejo/forgejo/pulls/3972): <!--number 3972 --><!--line 0 --><!--description YWRkIHN1cHBvcnQgZm9yIGJhc2ljIHJlcXVlc3QgdHJhY2luZyB3aXRoIG9wZW50ZWxlbWV0cnk=-->add support for basic request tracing with opentelemetry<!--description--> <!--end release-notes-assistant--> Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/3972 Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org> Co-authored-by: TheFox0x7 <thefox0x7@gmail.com> Co-committed-by: TheFox0x7 <thefox0x7@gmail.com>
90 lines
2.3 KiB
Go
90 lines
2.3 KiB
Go
// Copyright 2024 TheFox0x7. All rights reserved.
|
|
// SPDX-License-Identifier: EUPL-1.2
|
|
|
|
package opentelemetry
|
|
|
|
import (
|
|
"context"
|
|
"net/url"
|
|
"strings"
|
|
|
|
"code.gitea.io/gitea/modules/log"
|
|
"code.gitea.io/gitea/modules/setting"
|
|
|
|
"go.opentelemetry.io/otel/attribute"
|
|
"go.opentelemetry.io/otel/sdk/resource"
|
|
semconv "go.opentelemetry.io/otel/semconv/v1.25.0"
|
|
)
|
|
|
|
const (
|
|
decoderTelemetrySdk = "sdk"
|
|
decoderProcess = "process"
|
|
decoderOS = "os"
|
|
decoderHost = "host"
|
|
)
|
|
|
|
func newResource(ctx context.Context) (*resource.Resource, error) {
|
|
opts := []resource.Option{
|
|
resource.WithAttributes(parseSettingAttributes(setting.OpenTelemetry.ResourceAttributes)...),
|
|
}
|
|
opts = append(opts, parseDecoderOpts()...)
|
|
opts = append(opts, resource.WithAttributes(
|
|
semconv.ServiceName(setting.OpenTelemetry.ServiceName),
|
|
semconv.ServiceVersion(setting.ForgejoVersion),
|
|
))
|
|
return resource.New(ctx, opts...)
|
|
}
|
|
|
|
func parseDecoderOpts() []resource.Option {
|
|
var opts []resource.Option
|
|
for _, v := range strings.Split(setting.OpenTelemetry.ResourceDetectors, ",") {
|
|
switch v {
|
|
case decoderTelemetrySdk:
|
|
opts = append(opts, resource.WithTelemetrySDK())
|
|
case decoderProcess:
|
|
opts = append(opts, resource.WithProcess())
|
|
case decoderOS:
|
|
opts = append(opts, resource.WithOS())
|
|
case decoderHost:
|
|
opts = append(opts, resource.WithHost())
|
|
case "": // Don't warn on empty string
|
|
default:
|
|
log.Warn("Ignoring unknown resource decoder option: %s", v)
|
|
}
|
|
}
|
|
return opts
|
|
}
|
|
|
|
func parseSettingAttributes(s string) []attribute.KeyValue {
|
|
var attrs []attribute.KeyValue
|
|
rawAttrs := strings.TrimSpace(s)
|
|
|
|
if rawAttrs == "" {
|
|
return attrs
|
|
}
|
|
|
|
pairs := strings.Split(rawAttrs, ",")
|
|
|
|
var invalid []string
|
|
for _, p := range pairs {
|
|
k, v, found := strings.Cut(p, "=")
|
|
if !found {
|
|
invalid = append(invalid, p)
|
|
continue
|
|
}
|
|
key := strings.TrimSpace(k)
|
|
val, err := url.PathUnescape(strings.TrimSpace(v))
|
|
if err != nil {
|
|
// Retain original value if decoding fails, otherwise it will be
|
|
// an empty string.
|
|
val = v
|
|
log.Warn("Otel resource attribute decoding error, retaining unescaped value. key=%s, val=%s", key, val)
|
|
}
|
|
attrs = append(attrs, attribute.String(key, val))
|
|
}
|
|
if len(invalid) > 0 {
|
|
log.Warn("Partial resource, missing values: %v", invalid)
|
|
}
|
|
|
|
return attrs
|
|
}
|