mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-08 00:22:14 +01:00
406 lines
10 KiB
Go
406 lines
10 KiB
Go
// Code generated by gotmpl. DO NOT MODIFY.
|
|
// source: internal/shared/semconv/server.go.tmpl
|
|
|
|
// Copyright The OpenTelemetry Authors
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
// Package semconv provides OpenTelemetry semantic convention types and
|
|
// functionality.
|
|
package semconv // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv"
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"slices"
|
|
"strings"
|
|
"sync"
|
|
|
|
"go.opentelemetry.io/otel/attribute"
|
|
"go.opentelemetry.io/otel/codes"
|
|
"go.opentelemetry.io/otel/metric"
|
|
"go.opentelemetry.io/otel/semconv/v1.39.0"
|
|
"go.opentelemetry.io/otel/semconv/v1.39.0/httpconv"
|
|
)
|
|
|
|
type RequestTraceAttrsOpts struct {
|
|
// If set, this is used as value for the "http.client_ip" attribute.
|
|
HTTPClientIP string
|
|
}
|
|
|
|
type ResponseTelemetry struct {
|
|
StatusCode int
|
|
ReadBytes int64
|
|
ReadError error
|
|
WriteBytes int64
|
|
WriteError error
|
|
}
|
|
|
|
type HTTPServer struct {
|
|
requestBodySizeHistogram httpconv.ServerRequestBodySize
|
|
responseBodySizeHistogram httpconv.ServerResponseBodySize
|
|
requestDurationHistogram httpconv.ServerRequestDuration
|
|
}
|
|
|
|
func NewHTTPServer(meter metric.Meter) HTTPServer {
|
|
server := HTTPServer{}
|
|
|
|
var err error
|
|
server.requestBodySizeHistogram, err = httpconv.NewServerRequestBodySize(meter)
|
|
handleErr(err)
|
|
|
|
server.responseBodySizeHistogram, err = httpconv.NewServerResponseBodySize(meter)
|
|
handleErr(err)
|
|
|
|
server.requestDurationHistogram, err = httpconv.NewServerRequestDuration(
|
|
meter,
|
|
metric.WithExplicitBucketBoundaries(
|
|
0.005, 0.01, 0.025, 0.05, 0.075, 0.1,
|
|
0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10,
|
|
),
|
|
)
|
|
handleErr(err)
|
|
return server
|
|
}
|
|
|
|
// Status returns a span status code and message for an HTTP status code
|
|
// value returned by a server.
|
|
func (n HTTPServer) Status(code int) (codes.Code, string) {
|
|
if code == 0 {
|
|
return codes.Error, "No HTTP status code"
|
|
}
|
|
if code < 100 || code >= 600 {
|
|
return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code)
|
|
}
|
|
if code >= 400 {
|
|
return codes.Error, http.StatusText(code)
|
|
}
|
|
return codes.Ok, ""
|
|
}
|
|
|
|
// 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 (n HTTPServer) RequestTraceAttrs(server string, req *http.Request, opts RequestTraceAttrsOpts) []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)
|
|
|
|
peer, peerPort := SplitHostPort(req.RemoteAddr)
|
|
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.
|
|
count++
|
|
if peerPort > 0 {
|
|
count++
|
|
}
|
|
}
|
|
|
|
useragent := req.UserAgent()
|
|
if useragent != "" {
|
|
count++
|
|
}
|
|
|
|
// For client IP, use, in order:
|
|
// 1. The value passed in the options
|
|
// 2. The value in the X-Forwarded-For header
|
|
// 3. The peer address
|
|
clientIP := opts.HTTPClientIP
|
|
if clientIP == "" {
|
|
clientIP = serverClientIP(req.Header.Get("X-Forwarded-For"))
|
|
if clientIP == "" {
|
|
clientIP = peer
|
|
}
|
|
}
|
|
if clientIP != "" {
|
|
count++
|
|
}
|
|
|
|
if req.URL != nil && req.URL.Path != "" {
|
|
count++
|
|
}
|
|
|
|
protoName, protoVersion := netProtocol(req.Proto)
|
|
if protoName != "" && protoName != "http" {
|
|
count++
|
|
}
|
|
if protoVersion != "" {
|
|
count++
|
|
}
|
|
|
|
route := httpRoute(req.Pattern)
|
|
if route != "" {
|
|
count++
|
|
}
|
|
|
|
attrs := make([]attribute.KeyValue, 0, count)
|
|
attrs = append(attrs,
|
|
semconv.ServerAddress(host),
|
|
method,
|
|
scheme,
|
|
)
|
|
|
|
if hostPort > 0 {
|
|
attrs = append(attrs, semconv.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, semconv.NetworkPeerAddress(peer))
|
|
if peerPort > 0 {
|
|
attrs = append(attrs, semconv.NetworkPeerPort(peerPort))
|
|
}
|
|
}
|
|
|
|
if useragent != "" {
|
|
attrs = append(attrs, semconv.UserAgentOriginal(useragent))
|
|
}
|
|
|
|
if clientIP != "" {
|
|
attrs = append(attrs, semconv.ClientAddress(clientIP))
|
|
}
|
|
|
|
if req.URL != nil && req.URL.Path != "" {
|
|
attrs = append(attrs, semconv.URLPath(req.URL.Path))
|
|
}
|
|
|
|
if protoName != "" && protoName != "http" {
|
|
attrs = append(attrs, semconv.NetworkProtocolName(protoName))
|
|
}
|
|
if protoVersion != "" {
|
|
attrs = append(attrs, semconv.NetworkProtocolVersion(protoVersion))
|
|
}
|
|
|
|
if route != "" {
|
|
attrs = append(attrs, n.Route(route))
|
|
}
|
|
|
|
return attrs
|
|
}
|
|
|
|
func (s HTTPServer) NetworkTransportAttr(network string) []attribute.KeyValue {
|
|
attr := semconv.NetworkTransportPipe
|
|
switch network {
|
|
case "tcp", "tcp4", "tcp6":
|
|
attr = semconv.NetworkTransportTCP
|
|
case "udp", "udp4", "udp6":
|
|
attr = semconv.NetworkTransportUDP
|
|
case "unix", "unixgram", "unixpacket":
|
|
attr = semconv.NetworkTransportUnix
|
|
}
|
|
|
|
return []attribute.KeyValue{attr}
|
|
}
|
|
|
|
type ServerMetricData struct {
|
|
ServerName string
|
|
ResponseSize int64
|
|
|
|
MetricData
|
|
MetricAttributes
|
|
}
|
|
|
|
type MetricAttributes struct {
|
|
Req *http.Request
|
|
StatusCode int
|
|
Route string
|
|
AdditionalAttributes []attribute.KeyValue
|
|
}
|
|
|
|
type MetricData struct {
|
|
RequestSize int64
|
|
|
|
// The request duration, in milliseconds
|
|
ElapsedTime float64
|
|
}
|
|
|
|
var (
|
|
metricAddOptionPool = &sync.Pool{
|
|
New: func() any {
|
|
return &[]metric.AddOption{}
|
|
},
|
|
}
|
|
|
|
metricRecordOptionPool = &sync.Pool{
|
|
New: func() any {
|
|
return &[]metric.RecordOption{}
|
|
},
|
|
}
|
|
)
|
|
|
|
func (n HTTPServer) RecordMetrics(ctx context.Context, md ServerMetricData) {
|
|
attributes := n.MetricAttributes(md.ServerName, md.Req, md.StatusCode, md.Route, md.AdditionalAttributes)
|
|
o := metric.WithAttributeSet(attribute.NewSet(attributes...))
|
|
recordOpts := metricRecordOptionPool.Get().(*[]metric.RecordOption)
|
|
*recordOpts = append(*recordOpts, o)
|
|
n.requestBodySizeHistogram.Inst().Record(ctx, md.RequestSize, *recordOpts...)
|
|
n.responseBodySizeHistogram.Inst().Record(ctx, md.ResponseSize, *recordOpts...)
|
|
n.requestDurationHistogram.Inst().Record(ctx, md.ElapsedTime/1000.0, o)
|
|
*recordOpts = (*recordOpts)[:0]
|
|
metricRecordOptionPool.Put(recordOpts)
|
|
}
|
|
|
|
func (n HTTPServer) method(method string) (attribute.KeyValue, attribute.KeyValue) {
|
|
if method == "" {
|
|
return semconv.HTTPRequestMethodGet, attribute.KeyValue{}
|
|
}
|
|
if attr, ok := methodLookup[method]; ok {
|
|
return attr, attribute.KeyValue{}
|
|
}
|
|
|
|
orig := semconv.HTTPRequestMethodOriginal(method)
|
|
if attr, ok := methodLookup[strings.ToUpper(method)]; ok {
|
|
return attr, orig
|
|
}
|
|
return semconv.HTTPRequestMethodGet, orig
|
|
}
|
|
|
|
func (n HTTPServer) scheme(https bool) attribute.KeyValue { //nolint:revive // ignore linter
|
|
if https {
|
|
return semconv.URLScheme("https")
|
|
}
|
|
return semconv.URLScheme("http")
|
|
}
|
|
|
|
// 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 (n HTTPServer) 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,
|
|
semconv.HTTPRequestBodySize(int(resp.ReadBytes)),
|
|
)
|
|
}
|
|
if resp.WriteBytes > 0 {
|
|
attributes = append(attributes,
|
|
semconv.HTTPResponseBodySize(int(resp.WriteBytes)),
|
|
)
|
|
}
|
|
if resp.StatusCode > 0 {
|
|
attributes = append(attributes,
|
|
semconv.HTTPResponseStatusCode(resp.StatusCode),
|
|
)
|
|
}
|
|
|
|
return attributes
|
|
}
|
|
|
|
// Route returns the attribute for the route.
|
|
func (n HTTPServer) Route(route string) attribute.KeyValue {
|
|
return semconv.HTTPRoute(route)
|
|
}
|
|
|
|
func (n HTTPServer) MetricAttributes(server string, req *http.Request, statusCode int, route string, additionalAttributes []attribute.KeyValue) []attribute.KeyValue {
|
|
num := 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 {
|
|
num++
|
|
}
|
|
protoName, protoVersion := netProtocol(req.Proto)
|
|
if protoName != "" {
|
|
num++
|
|
}
|
|
if protoVersion != "" {
|
|
num++
|
|
}
|
|
|
|
if statusCode > 0 {
|
|
num++
|
|
}
|
|
|
|
if route != "" {
|
|
num++
|
|
}
|
|
|
|
attributes := slices.Grow(additionalAttributes, num)
|
|
attributes = append(attributes,
|
|
semconv.HTTPRequestMethodKey.String(standardizeHTTPMethod(req.Method)),
|
|
n.scheme(req.TLS != nil),
|
|
semconv.ServerAddress(host))
|
|
|
|
if hostPort > 0 {
|
|
attributes = append(attributes, semconv.ServerPort(hostPort))
|
|
}
|
|
if protoName != "" {
|
|
attributes = append(attributes, semconv.NetworkProtocolName(protoName))
|
|
}
|
|
if protoVersion != "" {
|
|
attributes = append(attributes, semconv.NetworkProtocolVersion(protoVersion))
|
|
}
|
|
|
|
if statusCode > 0 {
|
|
attributes = append(attributes, semconv.HTTPResponseStatusCode(statusCode))
|
|
}
|
|
|
|
if route != "" {
|
|
attributes = append(attributes, semconv.HTTPRoute(route))
|
|
}
|
|
return attributes
|
|
}
|