Commit 190e6280 by Johannes Stelzer

publish logfile via actuate endpoint not via controller

parent 7b1e6906
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package de.codecentric.boot.admin.controller; package de.codecentric.boot.admin.actuate;
import java.io.IOException; import java.io.IOException;
...@@ -22,6 +22,9 @@ import javax.servlet.http.HttpServletResponse; ...@@ -22,6 +22,9 @@ import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
...@@ -30,62 +33,108 @@ import org.springframework.http.ResponseEntity; ...@@ -30,62 +33,108 @@ import org.springframework.http.ResponseEntity;
import org.springframework.util.FileCopyUtils; import org.springframework.util.FileCopyUtils;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.ResponseBody;
/** /**
* Controller that provides an API for logfiles, i.e. downloading the main logfile configured in environment property * 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. * 'logging.file' that is standard, but optional property for spring-boot applications.
*/ */
@RestController @ConfigurationProperties(prefix = "endpoints.logfile")
public class LogfileController { public class LogfileMvcEndpoint implements MvcEndpoint {
private static final Logger LOGGER = LoggerFactory.getLogger(LogfileController.class); private static final Logger LOGGER = LoggerFactory.getLogger(LogfileMvcEndpoint.class);
@Value("${logging.file}") @Value("${logging.file}")
private String logfile; private String logfile;
@RequestMapping(value = "/logfile", method = { RequestMethod.GET }) private String path = "/logfile";
public String getLogfile(HttpServletResponse response) {
if (logfile == null) { private boolean sensitive = true;
LOGGER.error("Logfile download failed for missing property 'logging.file'");
response.setStatus(HttpStatus.NOT_FOUND.value()); private boolean enabled = true;
return "Logfile download failed for missing property 'logging.file'";
@Override
public boolean isSensitive() {
return sensitive;
} }
Resource file = new FileSystemResource(logfile); @Override
if (!file.exists()) { public String getPath() {
LOGGER.error("Logfile download failed for missing file at path={}", logfile); return path;
}
@Override
@SuppressWarnings("rawtypes")
public Class<? extends Endpoint> getEndpointType() {
return null;
}
public void setLogfile(String logfile) {
this.logfile = logfile;
}
public String getLogfile() {
return logfile;
}
public void setPath(String path) {
this.path = path;
}
public void setSensitive(boolean sensitive) {
this.sensitive = sensitive;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public boolean isEnabled() {
return enabled;
}
@RequestMapping(method = RequestMethod.GET)
public void invoke(HttpServletResponse response) throws IOException {
if (!isAvailable()) {
response.setStatus(HttpStatus.NOT_FOUND.value()); response.setStatus(HttpStatus.NOT_FOUND.value());
return "Logfile download failed for missing file at path=" + logfile; return;
} }
response.setContentType(MediaType.TEXT_PLAIN_VALUE);
response.setHeader("Content-Disposition", "attachment; filename=\"" + file.getFilename() + "\"");
try { Resource file = new FileSystemResource(logfile);
response.setContentType(MediaType.TEXT_PLAIN_VALUE);
FileCopyUtils.copy(file.getInputStream(), response.getOutputStream()); FileCopyUtils.copy(file.getInputStream(), response.getOutputStream());
} catch (IOException ex) {
LOGGER.error("Logfile download failed for path={}. Reasond: {}", logfile, ex.getMessage());
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
return "Logfile download failed for path=" + logfile;
} }
return null;
@RequestMapping(method = RequestMethod.HEAD)
@ResponseBody
public ResponseEntity<Void> available() {
if (isAvailable()) {
return new ResponseEntity<Void>(HttpStatus.OK);
}
else {
return new ResponseEntity<Void>(HttpStatus.NOT_FOUND);
}
}
private boolean isAvailable() {
if (!enabled) {
return false;
} }
@RequestMapping(value = "/logfile", method = { RequestMethod.HEAD })
public ResponseEntity<?> hasLogfile() {
if (logfile == null) { if (logfile == null) {
return new ResponseEntity<Void>(HttpStatus.NOT_FOUND); LOGGER.error("Logfile download failed for missing property 'logging.file'");
return false;
} }
Resource file = new FileSystemResource(logfile); Resource file = new FileSystemResource(logfile);
if (!file.exists()) { if (!file.exists()) {
return new ResponseEntity<Void>(HttpStatus.NOT_FOUND); LOGGER.error("Logfile download failed for missing file at path={}", logfile);
return false;
} }
return new ResponseEntity<Void>(HttpStatus.OK); return true;
} }
public void setLogfile(String logfile) {
this.logfile = logfile;
}
} }
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
*/ */
package de.codecentric.boot.admin.config; package de.codecentric.boot.admin.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
...@@ -23,7 +24,7 @@ import org.springframework.http.converter.json.MappingJackson2HttpMessageConvert ...@@ -23,7 +24,7 @@ import org.springframework.http.converter.json.MappingJackson2HttpMessageConvert
import org.springframework.scheduling.config.ScheduledTaskRegistrar; import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import de.codecentric.boot.admin.controller.LogfileController; import de.codecentric.boot.admin.actuate.LogfileMvcEndpoint;
import de.codecentric.boot.admin.services.SpringBootAdminRegistrator; import de.codecentric.boot.admin.services.SpringBootAdminRegistrator;
import de.codecentric.boot.admin.web.SimpleCORSFilter; import de.codecentric.boot.admin.web.SimpleCORSFilter;
...@@ -79,13 +80,16 @@ public class SpringBootAdminClientAutoConfiguration { ...@@ -79,13 +80,16 @@ public class SpringBootAdminClientAutoConfiguration {
return registrar; return registrar;
} }
@Configuration
@ConditionalOnExpression("${endpoints.logfile.enabled:true}")
public static class LogfileEndpointAutoConfiguration {
/** /**
* Controller to do something with the application logfile(s). * Exposes the logfile as acutator endpoint
*/ */
@Bean @Bean
@ConditionalOnProperty("logging.file") public LogfileMvcEndpoint logfileEndpoint() {
public LogfileController logfileController() { return new LogfileMvcEndpoint();
return new LogfileController(); }
} }
} }
package de.codecentric.boot.admin.controller; /*
* Copyright 2014 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 de.codecentric.boot.admin.actuate;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
...@@ -10,27 +25,28 @@ import org.springframework.http.HttpStatus; ...@@ -10,27 +25,28 @@ import org.springframework.http.HttpStatus;
import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.util.FileCopyUtils; import org.springframework.util.FileCopyUtils;
public class LogfileControllerTest {
private LogfileController controller = new LogfileController(); public class LogfileMvcEndpointTest {
private LogfileMvcEndpoint controller = new LogfileMvcEndpoint();
@Test @Test
public void logfile_noProperty() { public void logfile_noProperty() throws IOException {
assertEquals(HttpStatus.NOT_FOUND, controller.hasLogfile().getStatusCode()); assertEquals(HttpStatus.NOT_FOUND, controller.available().getStatusCode());
MockHttpServletResponse response = new MockHttpServletResponse(); MockHttpServletResponse response = new MockHttpServletResponse();
controller.getLogfile(response); controller.invoke(response);
assertEquals(HttpStatus.NOT_FOUND.value(), response.getStatus()); assertEquals(HttpStatus.NOT_FOUND.value(), response.getStatus());
} }
@Test @Test
public void logfile_noFile() { public void logfile_noFile() throws IOException {
controller.setLogfile("does_not_exist.log"); controller.setLogfile("does_not_exist.log");
assertEquals(HttpStatus.NOT_FOUND, controller.hasLogfile().getStatusCode()); assertEquals(HttpStatus.NOT_FOUND, controller.available().getStatusCode());
MockHttpServletResponse response = new MockHttpServletResponse(); MockHttpServletResponse response = new MockHttpServletResponse();
controller.getLogfile(response); controller.invoke(response);
assertEquals(HttpStatus.NOT_FOUND.value(), response.getStatus()); assertEquals(HttpStatus.NOT_FOUND.value(), response.getStatus());
} }
...@@ -40,13 +56,14 @@ public class LogfileControllerTest { ...@@ -40,13 +56,14 @@ public class LogfileControllerTest {
FileCopyUtils.copy("--TEST--".getBytes(), new File("test.log")); FileCopyUtils.copy("--TEST--".getBytes(), new File("test.log"));
controller.setLogfile("test.log"); controller.setLogfile("test.log");
assertEquals(HttpStatus.OK, controller.hasLogfile().getStatusCode()); assertEquals(HttpStatus.OK, controller.available().getStatusCode());
MockHttpServletResponse response = new MockHttpServletResponse(); MockHttpServletResponse response = new MockHttpServletResponse();
controller.getLogfile(response); controller.invoke(response);
assertEquals(HttpStatus.OK.value(), response.getStatus()); assertEquals(HttpStatus.OK.value(), response.getStatus());
assertEquals("--TEST--", response.getContentAsString()); assertEquals("--TEST--", response.getContentAsString());
} finally { }
finally {
new File("test.log").delete(); new File("test.log").delete();
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment