Add /logfile MVC actuator endpoint
Add a `/logfile` endpoint which can be used to fetch the contents of the log file (if one is being used). Fixes gh-2137 Closes gh-2294
This commit is contained in:
parent
880f31ae2e
commit
308a5eaff5
|
@ -45,6 +45,7 @@ import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping;
|
||||||
import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMappingCustomizer;
|
import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMappingCustomizer;
|
||||||
import org.springframework.boot.actuate.endpoint.mvc.EnvironmentMvcEndpoint;
|
import org.springframework.boot.actuate.endpoint.mvc.EnvironmentMvcEndpoint;
|
||||||
import org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint;
|
import org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint;
|
||||||
|
import org.springframework.boot.actuate.endpoint.mvc.LogFileMvcEndpoint;
|
||||||
import org.springframework.boot.actuate.endpoint.mvc.MetricsMvcEndpoint;
|
import org.springframework.boot.actuate.endpoint.mvc.MetricsMvcEndpoint;
|
||||||
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
|
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
|
||||||
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoints;
|
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoints;
|
||||||
|
@ -90,6 +91,7 @@ import org.springframework.web.servlet.DispatcherServlet;
|
||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
* @author Christian Dupuis
|
* @author Christian Dupuis
|
||||||
* @author Andy Wilkinson
|
* @author Andy Wilkinson
|
||||||
|
* @author Johannes Stelzer
|
||||||
*/
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
|
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
|
||||||
|
@ -219,6 +221,12 @@ public class EndpointWebMvcAutoConfiguration implements ApplicationContextAware,
|
||||||
return new MetricsMvcEndpoint(delegate);
|
return new MetricsMvcEndpoint(delegate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnEnabledEndpoint("logfile")
|
||||||
|
public LogFileMvcEndpoint logfileMvcEndpoint() {
|
||||||
|
return new LogFileMvcEndpoint();
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnBean(ShutdownEndpoint.class)
|
@ConditionalOnBean(ShutdownEndpoint.class)
|
||||||
@ConditionalOnEnabledEndpoint(value = "shutdown", enabledByDefault = false)
|
@ConditionalOnEnabledEndpoint(value = "shutdown", enabledByDefault = false)
|
||||||
|
|
|
@ -16,11 +16,7 @@
|
||||||
|
|
||||||
package org.springframework.boot.actuate.endpoint.mvc;
|
package org.springframework.boot.actuate.endpoint.mvc;
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.springframework.boot.actuate.endpoint.Endpoint;
|
import org.springframework.boot.actuate.endpoint.Endpoint;
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
@ -35,10 +31,6 @@ import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
*/
|
*/
|
||||||
public class EndpointMvcAdapter implements MvcEndpoint {
|
public class EndpointMvcAdapter implements MvcEndpoint {
|
||||||
|
|
||||||
private final ResponseEntity<Map<String, String>> disabledResponse = new ResponseEntity<Map<String, String>>(
|
|
||||||
Collections.singletonMap("message", "This endpoint is disabled"),
|
|
||||||
HttpStatus.NOT_FOUND);
|
|
||||||
|
|
||||||
private final Endpoint<?> delegate;
|
private final Endpoint<?> delegate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -54,9 +46,8 @@ public class EndpointMvcAdapter implements MvcEndpoint {
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
public Object invoke() {
|
public Object invoke() {
|
||||||
if (!this.delegate.isEnabled()) {
|
if (!this.delegate.isEnabled()) {
|
||||||
// Shouldn't happen - MVC endpoint shouldn't be registered when delegate's
|
// Shouldn't happen - shouldn't be registered when delegate's disabled
|
||||||
// disabled
|
return getDisabledResponse();
|
||||||
return this.disabledResponse;
|
|
||||||
}
|
}
|
||||||
return this.delegate.invoke();
|
return this.delegate.invoke();
|
||||||
}
|
}
|
||||||
|
@ -89,7 +80,7 @@ public class EndpointMvcAdapter implements MvcEndpoint {
|
||||||
* @return The response to be returned when the endpoint is disabled
|
* @return The response to be returned when the endpoint is disabled
|
||||||
*/
|
*/
|
||||||
protected ResponseEntity<?> getDisabledResponse() {
|
protected ResponseEntity<?> getDisabledResponse() {
|
||||||
return this.disabledResponse;
|
return MvcEndpoint.DISABLED_RESPONSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,152 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2015 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.boot.actuate.endpoint.mvc;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
import javax.validation.constraints.Pattern;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.springframework.boot.actuate.endpoint.Endpoint;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.boot.logging.LogFile;
|
||||||
|
import org.springframework.context.EnvironmentAware;
|
||||||
|
import org.springframework.core.env.Environment;
|
||||||
|
import org.springframework.core.io.FileSystemResource;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.http.ResponseEntity.BodyBuilder;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMethod;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller that provides an API for logfiles, i.e. downloading the main logfile
|
||||||
|
* configured in environment property 'logging.file' that is standard, but optional
|
||||||
|
* property for spring-boot applications.
|
||||||
|
*
|
||||||
|
* @author Johannes Stelzer
|
||||||
|
* @author Phillip Webb
|
||||||
|
* @since 1.3.0
|
||||||
|
*/
|
||||||
|
@ConfigurationProperties(prefix = "endpoints.logfile")
|
||||||
|
public class LogFileMvcEndpoint implements MvcEndpoint, EnvironmentAware {
|
||||||
|
|
||||||
|
private static final Log logger = LogFactory.getLog(LogFileMvcEndpoint.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Endpoint URL path.
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
@Pattern(regexp = "/[^/]*", message = "Path must start with /")
|
||||||
|
private String path = "/logfile";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable security on the endpoint.
|
||||||
|
*/
|
||||||
|
|
||||||
|
private boolean sensitive = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable the endpoint.
|
||||||
|
*/
|
||||||
|
|
||||||
|
private boolean enabled = true;
|
||||||
|
|
||||||
|
private Environment environment;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEnvironment(Environment environment) {
|
||||||
|
this.environment = environment;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPath() {
|
||||||
|
return this.path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPath(String path) {
|
||||||
|
this.path = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSensitive() {
|
||||||
|
return this.sensitive;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSensitive(boolean sensitive) {
|
||||||
|
this.sensitive = sensitive;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return this.enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnabled(boolean enabled) {
|
||||||
|
this.enabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
public Class<? extends Endpoint> getEndpointType() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequestMapping(method = RequestMethod.HEAD)
|
||||||
|
@ResponseBody
|
||||||
|
public ResponseEntity<?> available() {
|
||||||
|
return getResponse(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequestMapping(method = RequestMethod.GET)
|
||||||
|
@ResponseBody
|
||||||
|
public ResponseEntity<?> invoke() throws IOException {
|
||||||
|
return getResponse(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ResponseEntity<?> getResponse(boolean includeBody) {
|
||||||
|
if (!isEnabled()) {
|
||||||
|
return (includeBody ? DISABLED_RESPONSE : ResponseEntity.notFound().build());
|
||||||
|
}
|
||||||
|
Resource resource = getLogFileResource();
|
||||||
|
if (resource == null) {
|
||||||
|
return ResponseEntity.notFound().build();
|
||||||
|
}
|
||||||
|
BodyBuilder response = ResponseEntity.ok().contentType(MediaType.TEXT_PLAIN);
|
||||||
|
return (includeBody ? response.body(resource) : response.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Resource getLogFileResource() {
|
||||||
|
LogFile logFile = LogFile.get(this.environment);
|
||||||
|
if (logFile == null) {
|
||||||
|
logger.debug("Missing 'logging.file' or 'logging.path' properties");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
FileSystemResource resource = new FileSystemResource(logFile.toString());
|
||||||
|
if (!resource.exists()) {
|
||||||
|
if (logger.isWarnEnabled()) {
|
||||||
|
logger.debug("Log file '" + resource + "' does not exist");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -16,7 +16,12 @@
|
||||||
|
|
||||||
package org.springframework.boot.actuate.endpoint.mvc;
|
package org.springframework.boot.actuate.endpoint.mvc;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import org.springframework.boot.actuate.endpoint.Endpoint;
|
import org.springframework.boot.actuate.endpoint.Endpoint;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A strategy for the MVC layer on top of an {@link Endpoint}. Implementations are allowed
|
* A strategy for the MVC layer on top of an {@link Endpoint}. Implementations are allowed
|
||||||
|
@ -29,6 +34,10 @@ import org.springframework.boot.actuate.endpoint.Endpoint;
|
||||||
*/
|
*/
|
||||||
public interface MvcEndpoint {
|
public interface MvcEndpoint {
|
||||||
|
|
||||||
|
public static final ResponseEntity<Map<String, String>> DISABLED_RESPONSE = new ResponseEntity<Map<String, String>>(
|
||||||
|
Collections.singletonMap("message", "This endpoint is disabled"),
|
||||||
|
HttpStatus.NOT_FOUND);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the MVC path of the endpoint.
|
* Return the MVC path of the endpoint.
|
||||||
* @return the endpoint path
|
* @return the endpoint path
|
||||||
|
|
|
@ -294,7 +294,7 @@ public class EndpointWebMvcAutoConfigurationTests {
|
||||||
this.applicationContext.refresh();
|
this.applicationContext.refresh();
|
||||||
// /health, /metrics, /env (/shutdown is disabled by default)
|
// /health, /metrics, /env (/shutdown is disabled by default)
|
||||||
assertThat(this.applicationContext.getBeansOfType(MvcEndpoint.class).size(),
|
assertThat(this.applicationContext.getBeansOfType(MvcEndpoint.class).size(),
|
||||||
is(equalTo(3)));
|
is(equalTo(4)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -18,6 +18,7 @@ package org.springframework.boot.actuate.endpoint.mvc;
|
||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.hamcrest.Matcher;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
@ -40,8 +41,9 @@ import org.springframework.web.context.WebApplicationContext;
|
||||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.containsString;
|
import static org.hamcrest.Matchers.containsString;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.hamcrest.Matchers.hasItem;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.hamcrest.Matchers.instanceOf;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
@ -73,10 +75,10 @@ public class JolokiaMvcEndpointTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||||
public void endpointRegistered() throws Exception {
|
public void endpointRegistered() throws Exception {
|
||||||
Set<? extends MvcEndpoint> values = this.endpoints.getEndpoints();
|
Set<? extends MvcEndpoint> values = this.endpoints.getEndpoints();
|
||||||
assertEquals(1, values.size());
|
assertThat(values, (Matcher) hasItem(instanceOf(JolokiaMvcEndpoint.class)));
|
||||||
assertTrue(values.iterator().next() instanceof JolokiaMvcEndpoint);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2013-2015 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.boot.actuate.endpoint.mvc;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.TemporaryFolder;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.mock.env.MockEnvironment;
|
||||||
|
import org.springframework.util.FileCopyUtils;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link LogFileMvcEndpoint}.
|
||||||
|
*
|
||||||
|
* @author Johannes Stelzer
|
||||||
|
* @author Phillip Webb
|
||||||
|
*/
|
||||||
|
public class LogFileMvcEndpointTests {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public TemporaryFolder temp = new TemporaryFolder();
|
||||||
|
|
||||||
|
private LogFileMvcEndpoint mvc;
|
||||||
|
|
||||||
|
private MockEnvironment environment;
|
||||||
|
|
||||||
|
private File logFile;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void before() throws IOException {
|
||||||
|
this.logFile = this.temp.newFile();
|
||||||
|
FileCopyUtils.copy("--TEST--".getBytes(), this.logFile);
|
||||||
|
this.environment = new MockEnvironment();
|
||||||
|
this.mvc = new LogFileMvcEndpoint();
|
||||||
|
this.mvc.setEnvironment(this.environment);
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void after() {
|
||||||
|
new File("test.log").delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void notAvailableWithoutLogFile() throws IOException {
|
||||||
|
assertThat(this.mvc.available().getStatusCode(), equalTo(HttpStatus.NOT_FOUND));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void notAvailableWithMissingLogFile() throws Exception {
|
||||||
|
this.environment.setProperty("logging.file", "no_test.log");
|
||||||
|
assertThat(this.mvc.available().getStatusCode(), equalTo(HttpStatus.NOT_FOUND));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void availableWithLogFile() throws Exception {
|
||||||
|
this.environment.setProperty("logging.file", this.logFile.getAbsolutePath());
|
||||||
|
assertThat(this.mvc.available().getStatusCode(), equalTo(HttpStatus.OK));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void notAvailableIfDisabled() throws Exception {
|
||||||
|
this.environment.setProperty("logging.file", this.logFile.getAbsolutePath());
|
||||||
|
this.mvc.setEnabled(false);
|
||||||
|
assertThat(this.mvc.available().getStatusCode(), equalTo(HttpStatus.NOT_FOUND));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void invokeGetsContent() throws IOException {
|
||||||
|
this.environment.setProperty("logging.file", this.logFile.getAbsolutePath());
|
||||||
|
ResponseEntity<?> response = this.mvc.invoke();
|
||||||
|
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||||
|
InputStream inputStream = ((Resource) response.getBody()).getInputStream();
|
||||||
|
InputStreamReader reader = new InputStreamReader(inputStream);
|
||||||
|
assertEquals("--TEST--", FileCopyUtils.copyToString(reader));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -643,6 +643,9 @@ content into your application; rather pick only the properties that you need.
|
||||||
endpoints.info.id=info
|
endpoints.info.id=info
|
||||||
endpoints.info.sensitive=false
|
endpoints.info.sensitive=false
|
||||||
endpoints.info.enabled=true
|
endpoints.info.enabled=true
|
||||||
|
endpoints.logfile.path=/logfile
|
||||||
|
endpoints.logfile.sensitive=true
|
||||||
|
endpoints.logfile.enabled=true
|
||||||
endpoints.mappings.enabled=true
|
endpoints.mappings.enabled=true
|
||||||
endpoints.mappings.id=mappings
|
endpoints.mappings.id=mappings
|
||||||
endpoints.mappings.sensitive=true
|
endpoints.mappings.sensitive=true
|
||||||
|
|
|
@ -94,6 +94,11 @@ unauthenticated connection or full message details when authenticated).
|
||||||
|Displays arbitrary application info.
|
|Displays arbitrary application info.
|
||||||
|false
|
|false
|
||||||
|
|
||||||
|
|`logfile`
|
||||||
|
|Returns the contents of the logfile (if `logging.file` or `logging.path` properties have
|
||||||
|
been set). Only available via MVC.
|
||||||
|
|true
|
||||||
|
|
||||||
|`metrics`
|
|`metrics`
|
||||||
|Shows '`metrics`' information for the current application.
|
|Shows '`metrics`' information for the current application.
|
||||||
|true
|
|true
|
||||||
|
|
Loading…
Reference in New Issue