Палец на пульсе: что там внутри вообще происходит?
Actuator — это набор готовых эндпойнтов, которые становятся доступны, стоит вам добавить зависимость. Звучит скучно. На деле вы превращаете свое приложение в шпиона, который сам на себя доносит.
Вот как это выглядит в коде:
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
И в application.yml — включаем то, что реально нужно, иначе по умолчанию покажут только /health и /info:
yaml
management:
endpoints:
web:
exposure:
include: "health,info,metrics,loggers,heapdump,threaddump,configprops"
base-path: /actuator # ну, стандарт, но вы можете поменять на /secret-doctor, если паранойя
endpoint:
health:
show-details: always
heapdump:
enabled: true
После этого на http://localhost:8080/actuator у вас консоль бога. Ну, или диспетчерская АЭС.
/health: когда прод жив, но дышит через раз
/health — это не просто {"status":"UP"}. Это как врать, что всё хорошо.
Можно сделать так, чтобы он честно говорил: «БД жива, Redis в коме, Kafka укатилась в закат, но приложение пока держится».
Пример кастомного индикатора: проверяем, что Ollama (наша нейросеть) отвечает быстрее чем за полсекунды. Если нет, пусть помечает как WARNING, но не валит весь health.
java
@Component
public class OllamaHealthIndicator implements HealthIndicator {
@Override
public Health health() {
long start = System.currentTimeMillis();
try {
restTemplate.getForObject("http://ollama:11434/api/tags", String.class);
long duration = System.currentTimeMillis() - start;
if (duration > 500) {
return Health.status("WARNING")
.withDetail("reason", "медленный ответ, тайм-аут " + duration + " мс")
.build();
}
return Health.up().withDetail("latency", duration).build();
} catch (Exception e) {
return Health.down().withException(e).build();
}
}
}
И теперь по /health видно: приложение в целом UP, но один из сервисов на ладан дышит.
/loggers: магия без перезагрузки
Самый недооцененный эндпойнт. Просто запомните этот прием: вы можете на лету включить DEBUG для конкретного пакета, не перезапуская приложение.
Сейчас у вас в коде:
java
log.info("Запрос пришел: {}", request);
Вы подозреваете, что творится какая-то муть внутри org.springframework.security. Достаточно отправить POST:
bash
curl -X POST http://localhost:8080/actuator/loggers/org.springframework.security
-H "Content-Type: application/json"
-d '{"configuredLevel": "DEBUG"}'
Вуаля. Льются все детали. Никакого redeploy, никакого docker restart. Это как попросить бармена налить покрепче, не вставая с табурета.
А когда наигрались, ставим обратно null, и он возвращается к исходному уровню.
/metrics: счетчик Гейгера для прода
Actuator уже собирает кучу всего: память, процессор, потоки, количество запросов к эндпойнтам. Но настоящий дзен — это кастомные метрики.
Хотите знать, сколько раз вызвали генерацию текста через Ollama и каким было среднее время ответа?
java
@RestController
public class GenerateController {
private final MeterRegistry meterRegistry;
private final Counter generateCounter;
private final Timer generateTimer;
public GenerateController(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
this.generateCounter = Counter.builder("ollama.generate.total")
.description("Сколько раз попросили нейросеть подумать")
.register(meterRegistry);
this.generateTimer = Timer.builder("ollama.generate.duration")
.description("Сколько нейросеть страдала")
.register(meterRegistry);
}
@PostMapping("/generate")
public String generate(@RequestBody Prompt prompt) {
generateCounter.increment();
return generateTimer.record(() -> {
// вызов Ollama...
return "привет, я нейросеть";
});
}
}
Теперь по /actuator/metrics/ollama.generate.total можно построить график в Prometheus и смотреть, не сошел ли мир с ума.
/heapdump и /threaddump: вскрытие показало, что пациент…
Когда всё совсем плохо (память течет, потоки повисли), самое время снять дамп.
/actuator/heapdump отдаст файл .hprof, который вы скормите VisualVM или JProfiler. Да, он большой. Да, скачивать неудобно. Но это единственный способ увидеть тот самый объект, который не дает GC уснуть по ночам.
/actuator/threaddump — легкий вариант: просто JSON со всеми стеками потоков. Глянули — увидели сотню потоков, висящих на wait() в каком-то пуле. Сразу поняли: пул маленький, задачи длинные, нужен Bulkhead. Или виски. Или и то и другое.
/configprops: а что у нас в конфигах на самом деле?
Знаете эту ситуацию: в application.yml вроде бы правильные настройки, а переменные окружения их переопределили, и никто не помнит, откуда взялось то самое значение?
/configprops показывает, какие значения реально попали в ваши @ConfigurationProperties. И главное — откуда: то ли из файла, то ли из SPRING_APPLICATION_JSON, то ли из параметра -D.
json
{
"ollama.client": {
"prefix": "ollama",
"properties": {
"url": {
"value": "http://ollama-prod:11434",
"origin": "System Environment Variable 'OLLAMA_URL'"
},
"timeout": {
"value": 45000,
"origin": "application.yml:17"
}
}
}
}
Свой эндпойнт: когда стандартных недостаточно
Actuator расширяется. Хотите показывать статус вашего любимого кеша или количество активных WebSocket-сессий?
java
@Component
@Endpoint(id = "websockets")
public class WebSocketEndpoint {
private final SimpUserRegistry userRegistry;
public WebSocketEndpoint(SimpUserRegistry userRegistry) {
this.userRegistry = userRegistry;
}
@ReadOperation
public Map<String, Object> stats() {
return Map.of(
"totalSessions", userRegistry.getUserCount(),
"users", userRegistry.getUsers().stream()
.map(SimpUser::getName)
.collect(Collectors.toList())
);
}
}
Вызовите /actuator/websockets — и вы увидите, сколько клиентов сейчас болтается на веб-сокетах. Красота.
Жизненный урок (и пара синяков)
Я бы мог рассказать вам красивую сказку про то, как Actuator спас продакшен в 3 часа ночи. Но вместо этого лучше честно предупрежу о граблях, на которые сам наступал:
Грабля первая. Не выставляйте Actuator наружу без защиты. /heapdump — это все секреты из памяти приложения. Все пароли, токены, ключи шифрования, переписка с бэкендом лежат в дампе. На проде либо закрывайте порт, либо вешайте отдельный management.port: 9001 и не открывайте наружу.
Грабля вторая. Включать все эндпойнты — плохая идея. Кто-то может переключить уровни логирования на TRACE на всех пакетах, и место на вашем диске кончится за 20 минут.
Грабля третья. /shutdown выключает приложение по POST-запросу. Включили случайно на проде — привет, неожиданная перезагрузка в обед. Оставьте для тестов, где вы точно знаете, кто его дергает.
Так что же в итоге?
Actuator дает возможность заглянуть внутрь живого организма и понять, почему он кашляет, а не бежит марафон.
Вы можете:
- менять уровень логов на лету (
/loggers); - считать бизнес-метрики (
/metrics); - смотреть реальные значения конфигов (
/configprops); - снимать дампы для глубокой отладки (
/heapdump); - писать свои эндпойнты под специфические нужды.
И всё это — без перезагрузок, деплоев и слёз (почти).
Лично для меня Actuator стал той самой точкой, после которой я перестал верить в то, что приложение работает, — и начал видеть, как оно живет. Это как молитва и рентген: оба дают надежду, но рентген еще и показывает, где именно у вас перелом.
Ветка actuator в моем проекте на GitVerse — там всё показано на примере нашего чата с Ollama. Дампы, метрики, кастомные индикаторы здоровья — берите, пробуйте, ломайте. Только осторожно: привыкнув к такому уровню контроля, к System.out.println() вы уже не вернетесь.
Бонус-шпаргалка для тех, кто не любит читать (а зря)
| Если вам нужно… | То делайте так… |
Посмотреть, жив ли сервис |
GET /actuator/health |
| Включить DEBUG для пакета на лету | POST /actuator/loggers/org.example с{"configuredLevel":"DEBUG"} |
| Узнать, сколько запросов к эндпойнту | GET /actuator/metrics/http.server.requests |
| Замерить время своей бизнес-операции | Timer из MeterRegistry |
| Найти утечку памяти | GET /actuator/heapdump → скормить VisualVM |
| Понять, откуда взялось значение в конфиге | GET /actuator/configprops |
| Расширить Actuator под свои нужды | @Endpoint(id = "my") + @ReadOperation |
Теперь идите и мониторьте с чувством, с толком, с расстановкой. И без лишней магии.