Пакет net/http/httptrace живёт в стандартной библиотеке Go начиная с версии 1.7, но большинство разработчиков им не пользуются. А зря — он даёт хуки на те этапы HTTP-запроса, которые обычно скрыты внутри транспорта: DNS-резолвинг, получение соединения, TLS-рукопожатие, момент отправки байтов на провод и момент прихода первого байта ответа.
Главная фишка — как он подключается. Никакого интерфейса Tracer у http.Client, никакой middleware. Вы прикрепляете структуру ClientTrace к context.Context через httptrace.WithClientTrace, а транспорт сам достаёт её через httptrace.ContextClientTrace в нужные моменты. Это сделано не случайно. Трейс путешествует вместе с запросом — любая middleware, которая пробрасывает контекст, автоматически тащит и трейсинг. Один http.Client может спокойно выполнять concurrent-запросы с разными трейсами, потому что мутабельного состояния на клиенте нет. А если трейс не прикрепили — транспорт просто пропускает проверку, издержки — один nil-check.
Сам ClientTrace — это структура с опциональными полями-функциями: DNSStart, DNSDone, ConnectStart, ConnectDone, TLSHandshakeStart, TLSHandshakeDone, GotConn, GotFirstResponseByte, WroteRequest и другие. Вы заполняете только те, что нужны. Остальные — nil, их движок пропускает.
Автор собирает две полезные штуки. Первая — CLI в стиле curl --trace. Просто замеряет time.Now() в каждом хуке и выводит раскладку: сколько ушло на DNS, TCP-подключение, TLS, ожидание первого байта от сервера (TTFB) и передачу контента. Никаких агентов и APM-библиотек, чистый stdlib. Важный нюанс: DNSStart/DNSDone не срабатывают, если адрес уже есть в кеше DNS ядра или передан напрямую IP. TLSHandshakeStart/TLSHandshakeDone» — только для HTTPS.GotConnпоказывает не только новое соединение, но и переиспользованное — уGotConnInfoесть полеReused`.
Вторая — обёртка http.RoundTripper. Она автоматически цепляет трейс к каждому запросу и логирует тайминги. Если вызывающий код уже прикрепил свой ClientTrace, повторный вызов WithClientTrace не заменяет его — оба хука сработают. Ещё нюанс: RoundTrip возвращается после чтения заголовков ответа, не дожидаясь тела. Чтобы замерить полное время, нужно обернуть res.Body в читалку, которая записывает time.Now() при Close().
Отдельно подсвечена проверка переиспользования соединений. Если GotConnInfo.Reused постоянно false, проблема почти всегда в том, что вы не закрываете тело ответа или создаёте новый http.Client на каждый запрос. Без httptrace это можно было бы поймать только сниффером.
Пакет — маленький, но дело своё знает. Одна функция прикрепить трейс, одна — достать, и структура с хуками. Контекстный дизайн легко встраивается в любую stdlib-архитектуру. Два примера в статье — CLI и RoundTripper — хватит, чтобы начать отлавливать медленные запросы до того, как лезть за распределённым трейсингом. Автор упоминает, что их команда из Limeleaf строит на этом инструмент probes.dev для мониторинга сайтов и API.