«1. Введение

Когда мы работаем с микросервисами или веб-сервисами в целом, очень полезно знать, как наши пользователи взаимодействуют с нашими сервисами. Этого можно добиться, отслеживая все запросы, поступающие в наши сервисы, и собирая эту информацию для последующего анализа.

Есть несколько доступных систем, которые могут помочь нам в этом и могут быть легко интегрированы со Spring, например Zipkin. Однако Spring Boot Actuator имеет эту встроенную функцию и может использоваться через конечную точку httpTrace, которая отслеживает все HTTP-запросы. В этом уроке мы покажем, как его использовать и как настроить, чтобы он лучше соответствовал нашим требованиям.

2. Настройка конечной точки HttpTrace

В этом руководстве мы будем использовать проект Maven Spring Boot.

Первое, что нам нужно сделать, это добавить в наш проект зависимость Spring Boot Actuator:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

После этого нам нужно включить конечную точку httpTrace в нашем приложении.

Для этого нам просто нужно изменить наш файл application.properties, включив в него конечную точку httpTrace:

management.endpoints.web.exposure.include=httptrace

Если нам нужно больше конечных точек, мы можем просто соединить их через запятую или включить все их с помощью подстановочного знака *.

Теперь наша конечная точка httpTrace должна появиться в списке конечных точек привода нашего приложения:

{
  "_links": {
    "self": {
      "href": "http://localhost:8080/actuator",
      "templated": false
    },
    "httptrace": {
      "href": "http://localhost:8080/actuator/httptrace",
      "templated": false
    }
  }
}

Обратите внимание, что мы можем перечислить все включенные конечные точки привода, перейдя в конечную точку /actuator нашего веб-сервиса.

3. Анализ трассировок

Давайте теперь проанализируем трассировки, которые возвращает конечная точка привода httpTrace.

Сделаем несколько запросов к нашему сервису, вызовем конечную точку /actuator/httptrace и возьмем один из возвращенных трейсов:

{
  "traces": [
    {
      "timestamp": "2019-08-05T19:28:36.353Z",
      "principal": null,
      "session": null,
      "request": {
        "method": "GET",
        "uri": "http://localhost:8080/echo?msg=test",
        "headers": {
          "accept-language": [
            "en-GB,en-US;q=0.9,en;q=0.8"
          ],
          "upgrade-insecure-requests": [
            "1"
          ],
          "host": [
            "localhost:8080"
          ],
          "connection": [
            "keep-alive"
          ],
          "accept-encoding": [
            "gzip, deflate, br"
          ],
          "accept": [
            "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8"
          ],
          "user-agent": [
            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36 OPR/62.0.3331.66"
          ]
        },
        "remoteAddress": null
      },
      "response": {
        "status": 200,
        "headers": {
          "Content-Length": [
            "12"
          ],
          "Date": [
            "Mon, 05 Aug 2019 19:28:36 GMT"
          ],
          "Content-Type": [
            "text/html;charset=UTF-8"
          ]
        }
      },
      "timeTaken": 82
    }
  ]
}

Как мы видим, ответ разбит на несколько узлов:

    метка времени: время, когда был получен запрос; основной: аутентифицированный пользователь, выполнивший запрос, если применимо; сеанс: любой сеанс, связанный с запросом; запрос: информация о запросе, например метод, полный URI или заголовки; статус или заголовки timeTaken: время, затраченное на обработку запроса

Мы можем адаптировать этот ответ к нашим потребностям, если мы чувствуем, что он слишком многословен. Мы можем сообщить Spring, какие поля мы хотим вернуть, указав их в свойстве management.trace.http.include нашего application.properties:

management.trace.http.include=RESPONSE_HEADERS

В этом случае мы указали, что нам нужны только заголовки ответа. Следовательно, мы видим, что поля, которые были включены ранее, такие как заголовки запроса или затраченное время, теперь отсутствуют в ответе:

{
  "traces": [
    {
      "timestamp": "2019-08-05T20:23:01.397Z",
      "principal": null,
      "session": null,
      "request": {
        "method": "GET",
        "uri": "http://localhost:8080/echo?msg=test",
        "headers": {},
        "remoteAddress": null
      },
      "response": {
        "status": 200,
        "headers": {
          "Content-Length": [
            "12"
          ],
          "Date": [
            "Mon, 05 Aug 2019 20:23:01 GMT"
          ],
          "Content-Type": [
            "text/html;charset=UTF-8"
          ]
        }
      },
      "timeTaken": null
    }
  ]
}

Все возможные значения, которые можно включить, можно найти в исходном коде, как а также стандартные.

4. Настройка HttpTraceRepository

По умолчанию конечная точка httpTrace возвращает только последние 100 запросов и сохраняет их в памяти. Хорошая новость заключается в том, что мы также можем настроить это, создав собственный HttpTraceRepository.

Давайте теперь создадим наш репозиторий. Интерфейс HttpTraceRepository очень прост, и нам нужно реализовать только два метода: findAll() для извлечения всех трассировок; и add(), чтобы добавить трассировку в репозиторий.

Для простоты наш репозиторий также будет хранить трассировки в памяти, и мы будем хранить только последний запрос GET, который попал в нашу службу:

@Repository
public class CustomTraceRepository implements HttpTraceRepository {

    AtomicReference<HttpTrace> lastTrace = new AtomicReference<>();

    @Override
    public List<HttpTrace> findAll() {
        return Collections.singletonList(lastTrace.get());
    }

    @Override
    public void add(HttpTrace trace) {
        if ("GET".equals(trace.getRequest().getMethod())) {
            lastTrace.set(trace);
        }
    }

}

Хотя этот простой пример может показаться не очень полезным, мы можем видеть насколько мощным это может быть и как мы можем хранить наши журналы где угодно.

5. Фильтрация путей для трассировки

Последнее, что мы собираемся рассмотреть, это как фильтровать пути, которые мы хотим отслеживать, чтобы мы могли игнорировать некоторые запросы, которые нас не интересуют. ~~ ~ Если мы немного поиграем с конечной точкой httpTrace после выполнения некоторых запросов к нашему сервису, мы увидим, что мы также получаем трассировки для запросов привода:

Мы можем найти эти трассировки бесполезными для нас, и мы предпочитаю их исключать. В этом случае нам просто нужно создать собственный HttpTraceFilter и указать, какие пути мы хотим игнорировать в методе shouldNotFilter:

{
  "traces": [
    {
      "timestamp": "2019-07-28T13:56:36.998Z",
      "principal": null,
      "session": null,
      "request": {
        "method": "GET",
        "uri": "http://localhost:8080/actuator/",
         // ...
}

«

@Component
public class TraceRequestFilter extends HttpTraceFilter {

  public TraceRequestFilter(HttpTraceRepository repository, HttpExchangeTracer tracer) {
      super(repository, tracer);
  }

  @Override
  protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
      return request.getServletPath().contains("actuator");
  }
}

«Обратите внимание, что HttpTraceFilter — это обычный фильтр Spring, но с некоторыми специфическими для трассировки функциями.

6. Заключение

В этом руководстве мы представили конечную точку httpTrace Spring Boot Actuator и показали ее основные функции. Мы также копнули немного глубже и объяснили, как изменить некоторые поведения по умолчанию, чтобы они лучше соответствовали нашим конкретным потребностям.

Как всегда, полный исходный код примеров доступен на GitHub.