http.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. // Copyright The OpenTelemetry Authors
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package internal // import "go.opentelemetry.io/otel/semconv/internal"
  15. import (
  16. "fmt"
  17. "net"
  18. "net/http"
  19. "strconv"
  20. "strings"
  21. "go.opentelemetry.io/otel/attribute"
  22. "go.opentelemetry.io/otel/codes"
  23. "go.opentelemetry.io/otel/trace"
  24. )
  25. // SemanticConventions are the semantic convention values defined for a
  26. // version of the OpenTelemetry specification.
  27. type SemanticConventions struct {
  28. EnduserIDKey attribute.Key
  29. HTTPClientIPKey attribute.Key
  30. HTTPFlavorKey attribute.Key
  31. HTTPHostKey attribute.Key
  32. HTTPMethodKey attribute.Key
  33. HTTPRequestContentLengthKey attribute.Key
  34. HTTPRouteKey attribute.Key
  35. HTTPSchemeHTTP attribute.KeyValue
  36. HTTPSchemeHTTPS attribute.KeyValue
  37. HTTPServerNameKey attribute.Key
  38. HTTPStatusCodeKey attribute.Key
  39. HTTPTargetKey attribute.Key
  40. HTTPURLKey attribute.Key
  41. HTTPUserAgentKey attribute.Key
  42. NetHostIPKey attribute.Key
  43. NetHostNameKey attribute.Key
  44. NetHostPortKey attribute.Key
  45. NetPeerIPKey attribute.Key
  46. NetPeerNameKey attribute.Key
  47. NetPeerPortKey attribute.Key
  48. NetTransportIP attribute.KeyValue
  49. NetTransportOther attribute.KeyValue
  50. NetTransportTCP attribute.KeyValue
  51. NetTransportUDP attribute.KeyValue
  52. NetTransportUnix attribute.KeyValue
  53. }
  54. // NetAttributesFromHTTPRequest generates attributes of the net
  55. // namespace as specified by the OpenTelemetry specification for a
  56. // span. The network parameter is a string that net.Dial function
  57. // from standard library can understand.
  58. func (sc *SemanticConventions) NetAttributesFromHTTPRequest(network string, request *http.Request) []attribute.KeyValue {
  59. attrs := []attribute.KeyValue{}
  60. switch network {
  61. case "tcp", "tcp4", "tcp6":
  62. attrs = append(attrs, sc.NetTransportTCP)
  63. case "udp", "udp4", "udp6":
  64. attrs = append(attrs, sc.NetTransportUDP)
  65. case "ip", "ip4", "ip6":
  66. attrs = append(attrs, sc.NetTransportIP)
  67. case "unix", "unixgram", "unixpacket":
  68. attrs = append(attrs, sc.NetTransportUnix)
  69. default:
  70. attrs = append(attrs, sc.NetTransportOther)
  71. }
  72. peerIP, peerName, peerPort := hostIPNamePort(request.RemoteAddr)
  73. if peerIP != "" {
  74. attrs = append(attrs, sc.NetPeerIPKey.String(peerIP))
  75. }
  76. if peerName != "" {
  77. attrs = append(attrs, sc.NetPeerNameKey.String(peerName))
  78. }
  79. if peerPort != 0 {
  80. attrs = append(attrs, sc.NetPeerPortKey.Int(peerPort))
  81. }
  82. hostIP, hostName, hostPort := "", "", 0
  83. for _, someHost := range []string{request.Host, request.Header.Get("Host"), request.URL.Host} {
  84. hostIP, hostName, hostPort = hostIPNamePort(someHost)
  85. if hostIP != "" || hostName != "" || hostPort != 0 {
  86. break
  87. }
  88. }
  89. if hostIP != "" {
  90. attrs = append(attrs, sc.NetHostIPKey.String(hostIP))
  91. }
  92. if hostName != "" {
  93. attrs = append(attrs, sc.NetHostNameKey.String(hostName))
  94. }
  95. if hostPort != 0 {
  96. attrs = append(attrs, sc.NetHostPortKey.Int(hostPort))
  97. }
  98. return attrs
  99. }
  100. // hostIPNamePort extracts the IP address, name and (optional) port from hostWithPort.
  101. // It handles both IPv4 and IPv6 addresses. If the host portion is not recognized
  102. // as a valid IPv4 or IPv6 address, the `ip` result will be empty and the
  103. // host portion will instead be returned in `name`.
  104. func hostIPNamePort(hostWithPort string) (ip string, name string, port int) {
  105. var (
  106. hostPart, portPart string
  107. parsedPort uint64
  108. err error
  109. )
  110. if hostPart, portPart, err = net.SplitHostPort(hostWithPort); err != nil {
  111. hostPart, portPart = hostWithPort, ""
  112. }
  113. if parsedIP := net.ParseIP(hostPart); parsedIP != nil {
  114. ip = parsedIP.String()
  115. } else {
  116. name = hostPart
  117. }
  118. if parsedPort, err = strconv.ParseUint(portPart, 10, 16); err == nil {
  119. port = int(parsedPort)
  120. }
  121. return
  122. }
  123. // EndUserAttributesFromHTTPRequest generates attributes of the
  124. // enduser namespace as specified by the OpenTelemetry specification
  125. // for a span.
  126. func (sc *SemanticConventions) EndUserAttributesFromHTTPRequest(request *http.Request) []attribute.KeyValue {
  127. if username, _, ok := request.BasicAuth(); ok {
  128. return []attribute.KeyValue{sc.EnduserIDKey.String(username)}
  129. }
  130. return nil
  131. }
  132. // HTTPClientAttributesFromHTTPRequest generates attributes of the
  133. // http namespace as specified by the OpenTelemetry specification for
  134. // a span on the client side.
  135. func (sc *SemanticConventions) HTTPClientAttributesFromHTTPRequest(request *http.Request) []attribute.KeyValue {
  136. attrs := []attribute.KeyValue{}
  137. // remove any username/password info that may be in the URL
  138. // before adding it to the attributes
  139. userinfo := request.URL.User
  140. request.URL.User = nil
  141. attrs = append(attrs, sc.HTTPURLKey.String(request.URL.String()))
  142. // restore any username/password info that was removed
  143. request.URL.User = userinfo
  144. return append(attrs, sc.httpCommonAttributesFromHTTPRequest(request)...)
  145. }
  146. func (sc *SemanticConventions) httpCommonAttributesFromHTTPRequest(request *http.Request) []attribute.KeyValue {
  147. attrs := []attribute.KeyValue{}
  148. if ua := request.UserAgent(); ua != "" {
  149. attrs = append(attrs, sc.HTTPUserAgentKey.String(ua))
  150. }
  151. if request.ContentLength > 0 {
  152. attrs = append(attrs, sc.HTTPRequestContentLengthKey.Int64(request.ContentLength))
  153. }
  154. return append(attrs, sc.httpBasicAttributesFromHTTPRequest(request)...)
  155. }
  156. func (sc *SemanticConventions) httpBasicAttributesFromHTTPRequest(request *http.Request) []attribute.KeyValue {
  157. // as these attributes are used by HTTPServerMetricAttributesFromHTTPRequest, they should be low-cardinality
  158. attrs := []attribute.KeyValue{}
  159. if request.TLS != nil {
  160. attrs = append(attrs, sc.HTTPSchemeHTTPS)
  161. } else {
  162. attrs = append(attrs, sc.HTTPSchemeHTTP)
  163. }
  164. if request.Host != "" {
  165. attrs = append(attrs, sc.HTTPHostKey.String(request.Host))
  166. } else if request.URL != nil && request.URL.Host != "" {
  167. attrs = append(attrs, sc.HTTPHostKey.String(request.URL.Host))
  168. }
  169. flavor := ""
  170. if request.ProtoMajor == 1 {
  171. flavor = fmt.Sprintf("1.%d", request.ProtoMinor)
  172. } else if request.ProtoMajor == 2 {
  173. flavor = "2"
  174. }
  175. if flavor != "" {
  176. attrs = append(attrs, sc.HTTPFlavorKey.String(flavor))
  177. }
  178. if request.Method != "" {
  179. attrs = append(attrs, sc.HTTPMethodKey.String(request.Method))
  180. } else {
  181. attrs = append(attrs, sc.HTTPMethodKey.String(http.MethodGet))
  182. }
  183. return attrs
  184. }
  185. // HTTPServerMetricAttributesFromHTTPRequest generates low-cardinality attributes
  186. // to be used with server-side HTTP metrics.
  187. func (sc *SemanticConventions) HTTPServerMetricAttributesFromHTTPRequest(serverName string, request *http.Request) []attribute.KeyValue {
  188. attrs := []attribute.KeyValue{}
  189. if serverName != "" {
  190. attrs = append(attrs, sc.HTTPServerNameKey.String(serverName))
  191. }
  192. return append(attrs, sc.httpBasicAttributesFromHTTPRequest(request)...)
  193. }
  194. // HTTPServerAttributesFromHTTPRequest generates attributes of the
  195. // http namespace as specified by the OpenTelemetry specification for
  196. // a span on the server side. Currently, only basic authentication is
  197. // supported.
  198. func (sc *SemanticConventions) HTTPServerAttributesFromHTTPRequest(serverName, route string, request *http.Request) []attribute.KeyValue {
  199. attrs := []attribute.KeyValue{
  200. sc.HTTPTargetKey.String(request.RequestURI),
  201. }
  202. if serverName != "" {
  203. attrs = append(attrs, sc.HTTPServerNameKey.String(serverName))
  204. }
  205. if route != "" {
  206. attrs = append(attrs, sc.HTTPRouteKey.String(route))
  207. }
  208. if values := request.Header["X-Forwarded-For"]; len(values) > 0 {
  209. addr := values[0]
  210. if i := strings.Index(addr, ","); i > 0 {
  211. addr = addr[:i]
  212. }
  213. attrs = append(attrs, sc.HTTPClientIPKey.String(addr))
  214. }
  215. return append(attrs, sc.httpCommonAttributesFromHTTPRequest(request)...)
  216. }
  217. // HTTPAttributesFromHTTPStatusCode generates attributes of the http
  218. // namespace as specified by the OpenTelemetry specification for a
  219. // span.
  220. func (sc *SemanticConventions) HTTPAttributesFromHTTPStatusCode(code int) []attribute.KeyValue {
  221. attrs := []attribute.KeyValue{
  222. sc.HTTPStatusCodeKey.Int(code),
  223. }
  224. return attrs
  225. }
  226. type codeRange struct {
  227. fromInclusive int
  228. toInclusive int
  229. }
  230. func (r codeRange) contains(code int) bool {
  231. return r.fromInclusive <= code && code <= r.toInclusive
  232. }
  233. var validRangesPerCategory = map[int][]codeRange{
  234. 1: {
  235. {http.StatusContinue, http.StatusEarlyHints},
  236. },
  237. 2: {
  238. {http.StatusOK, http.StatusAlreadyReported},
  239. {http.StatusIMUsed, http.StatusIMUsed},
  240. },
  241. 3: {
  242. {http.StatusMultipleChoices, http.StatusUseProxy},
  243. {http.StatusTemporaryRedirect, http.StatusPermanentRedirect},
  244. },
  245. 4: {
  246. {http.StatusBadRequest, http.StatusTeapot}, // yes, teapot is so useful…
  247. {http.StatusMisdirectedRequest, http.StatusUpgradeRequired},
  248. {http.StatusPreconditionRequired, http.StatusTooManyRequests},
  249. {http.StatusRequestHeaderFieldsTooLarge, http.StatusRequestHeaderFieldsTooLarge},
  250. {http.StatusUnavailableForLegalReasons, http.StatusUnavailableForLegalReasons},
  251. },
  252. 5: {
  253. {http.StatusInternalServerError, http.StatusLoopDetected},
  254. {http.StatusNotExtended, http.StatusNetworkAuthenticationRequired},
  255. },
  256. }
  257. // SpanStatusFromHTTPStatusCode generates a status code and a message
  258. // as specified by the OpenTelemetry specification for a span.
  259. func SpanStatusFromHTTPStatusCode(code int) (codes.Code, string) {
  260. spanCode, valid := validateHTTPStatusCode(code)
  261. if !valid {
  262. return spanCode, fmt.Sprintf("Invalid HTTP status code %d", code)
  263. }
  264. return spanCode, ""
  265. }
  266. // SpanStatusFromHTTPStatusCodeAndSpanKind generates a status code and a message
  267. // as specified by the OpenTelemetry specification for a span.
  268. // Exclude 4xx for SERVER to set the appropriate status.
  269. func SpanStatusFromHTTPStatusCodeAndSpanKind(code int, spanKind trace.SpanKind) (codes.Code, string) {
  270. spanCode, valid := validateHTTPStatusCode(code)
  271. if !valid {
  272. return spanCode, fmt.Sprintf("Invalid HTTP status code %d", code)
  273. }
  274. category := code / 100
  275. if spanKind == trace.SpanKindServer && category == 4 {
  276. return codes.Unset, ""
  277. }
  278. return spanCode, ""
  279. }
  280. // validateHTTPStatusCode validates the HTTP status code and returns
  281. // corresponding span status code. If the `code` is not a valid HTTP status
  282. // code, returns span status Error and false.
  283. func validateHTTPStatusCode(code int) (codes.Code, bool) {
  284. category := code / 100
  285. ranges, ok := validRangesPerCategory[category]
  286. if !ok {
  287. return codes.Error, false
  288. }
  289. ok = false
  290. for _, crange := range ranges {
  291. ok = crange.contains(code)
  292. if ok {
  293. break
  294. }
  295. }
  296. if !ok {
  297. return codes.Error, false
  298. }
  299. if category > 0 && category < 4 {
  300. return codes.Unset, true
  301. }
  302. return codes.Error, true
  303. }