package de.otto.edison.loggers;
import de.otto.edison.navigation.NavBar;
import de.otto.edison.navigation.NavBarItem;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.endpoint.LoggersEndpoint;
import org.springframework.boot.actuate.endpoint.LoggersEndpoint.LoggerLevels;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.logging.LogLevel;
import org.springframework.http.HttpEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
import static de.otto.edison.navigation.NavBarItem.navBarItem;
import static de.otto.edison.util.UrlHelper.baseUriOf;
import static java.util.stream.Collectors.toList;
import static org.springframework.boot.actuate.endpoint.mvc.ActuatorMediaTypes.APPLICATION_ACTUATOR_V1_JSON_VALUE;
import static org.springframework.boot.logging.LogLevel.valueOf;
import static org.springframework.http.MediaType.ALL_VALUE;
import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VALUE;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
import static org.springframework.http.MediaType.TEXT_HTML_VALUE;
import static org.springframework.http.ResponseEntity.notFound;
import static org.springframework.web.bind.annotation.RequestMethod.GET;
import static org.springframework.web.bind.annotation.RequestMethod.POST;
/**
* Replacement for {@link org.springframework.boot.actuate.endpoint.mvc.LoggersMvcEndpoint} with additional
* HTML UI to configure the log levels of the microservice.
*
* @since 1.1.0
*/
@Controller
@ConditionalOnProperty(prefix = "edison.loggers", name = "enabled", matchIfMissing = true)
public class LoggersHtmlEndpoint {
private final LoggersEndpoint loggersEndpoint;
@Autowired
public LoggersHtmlEndpoint(final LoggersEndpoint loggersEndpoint,
final NavBar rightNavBar) {
this.loggersEndpoint = loggersEndpoint;
rightNavBar.register(navBarItem(1, "Loggers", "internal/loggers"));
}
@RequestMapping(
value = "/internal/loggers",
produces = {
TEXT_HTML_VALUE,
ALL_VALUE},
method = GET)
public ModelAndView get(final HttpServletRequest request) {
return new ModelAndView("loggers", new HashMap<String,Object>() {{
put("loggers", getLoggers());
put("baseUri", baseUriOf(request));
}});
}
@RequestMapping(
value = "/internal/loggers",
produces = {
APPLICATION_ACTUATOR_V1_JSON_VALUE,
APPLICATION_JSON_VALUE},
method = GET)
@ResponseBody
public Object get() {
final Map<String, Object> levels = loggersEndpoint.invoke();
return (levels == null ? notFound().build() : levels);
}
@RequestMapping(
value = "/internal/loggers/{name:.*}",
produces = {
APPLICATION_ACTUATOR_V1_JSON_VALUE,
APPLICATION_JSON_VALUE},
method = GET)
@ResponseBody
public Object get(@PathVariable String name) {
final LoggerLevels levels = loggersEndpoint.invoke(name);
return (levels == null ? notFound().build() : levels);
}
@RequestMapping(
value = "/internal/loggers",
consumes = APPLICATION_FORM_URLENCODED_VALUE,
produces = TEXT_HTML_VALUE,
method = POST)
public RedirectView post(@ModelAttribute("name") String name,
@ModelAttribute("level") String level) {
final LogLevel logLevel = level == null ? null : valueOf(level.toUpperCase());
loggersEndpoint.setLogLevel(name, logLevel);
return new RedirectView("/internal/loggers");
}
@RequestMapping(
value = "/internal/loggers/{name:.*}",
consumes = {
APPLICATION_ACTUATOR_V1_JSON_VALUE,
APPLICATION_JSON_VALUE},
produces = {
APPLICATION_ACTUATOR_V1_JSON_VALUE,
APPLICATION_JSON_VALUE},
method = POST)
@ResponseBody
public Object post(@PathVariable String name,
@RequestBody Map<String, String> configuration) {
final String level = configuration.get("configuredLevel");
final LogLevel logLevel = level == null ? null : LogLevel.valueOf(level.toUpperCase());
loggersEndpoint.setLogLevel(name, logLevel);
return HttpEntity.EMPTY;
}
private List<Map<String,?>> getLoggers() {
@SuppressWarnings({"unchecked", "raw"})
final Map<String,?> loggers = (Map) loggersEndpoint.invoke().get("loggers");
return loggers
.keySet()
.stream()
.map(key -> key.contains("$") ? null : new HashMap<String,Object>() {{
final LoggerLevels logger = (LoggerLevels) loggers.get(key);
put("name", key);
put("displayName", displayNameOf(key));
put("configuredLevel", logger.getConfiguredLevel());
put("effectiveLevel", logger.getEffectiveLevel());
}})
.filter(Objects::nonNull)
.collect(toList());
}
private String displayNameOf(final String key) {
if (key.contains(".")) {
StringTokenizer tokenizer = new StringTokenizer(key, ".");
StringJoiner joiner = new StringJoiner(".");
int pos = 0;
while (tokenizer.hasMoreTokens()) {
final String word = tokenizer.nextToken();
if (tokenizer.hasMoreTokens() && pos > 1) {
joiner.add(word.substring(0,1));
} else {
joiner.add(word);
}
++pos;
}
return joiner.toString();
} else {
return key;
}
}
}