From 051d9a551861451d826859b64867e796ca873d49 Mon Sep 17 00:00:00 2001 From: Lukas Hinsch Date: Sat, 28 Mar 2015 15:48:03 +0100 Subject: [PATCH 1/3] Allow downloading only the last 50 lines of a file --- .../boot/actuator/logview/FileProvider.java | 7 ++++++ .../logview/FileSystemFileProvider.java | 25 +++++++++++++++++-- .../actuator/logview/LogViewEndpoint.java | 13 ++++++++-- lib/src/main/resources/templates/logview.ftl | 3 ++- .../actuator/logview/LogViewEndpointTest.java | 8 +++--- src/main/resources/application.properties | 4 +-- 6 files changed, 49 insertions(+), 11 deletions(-) diff --git a/lib/src/main/java/eu/hinsch/spring/boot/actuator/logview/FileProvider.java b/lib/src/main/java/eu/hinsch/spring/boot/actuator/logview/FileProvider.java index 7d8eeba..58c0307 100644 --- a/lib/src/main/java/eu/hinsch/spring/boot/actuator/logview/FileProvider.java +++ b/lib/src/main/java/eu/hinsch/spring/boot/actuator/logview/FileProvider.java @@ -9,7 +9,14 @@ * Created by lh on 28/02/15. */ public interface FileProvider { + boolean canHandle(Path folder); + List getFileEntries(Path folder) throws IOException; + void streamContent(Path folder, String filename, OutputStream stream) throws IOException; + + default void tailContent(Path folder, String filename, OutputStream stream, int lines) throws IOException { + throw new UnsupportedOperationException("by default no tailing possible"); + } } diff --git a/lib/src/main/java/eu/hinsch/spring/boot/actuator/logview/FileSystemFileProvider.java b/lib/src/main/java/eu/hinsch/spring/boot/actuator/logview/FileSystemFileProvider.java index df70e53..b47e720 100644 --- a/lib/src/main/java/eu/hinsch/spring/boot/actuator/logview/FileSystemFileProvider.java +++ b/lib/src/main/java/eu/hinsch/spring/boot/actuator/logview/FileSystemFileProvider.java @@ -1,7 +1,9 @@ package eu.hinsch.spring.boot.actuator.logview; import org.apache.commons.io.IOUtils; +import org.apache.commons.io.input.ReversedLinesFileReader; +import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.OutputStream; @@ -9,6 +11,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Collections; import java.util.Date; import java.util.List; @@ -31,7 +34,7 @@ public List getFileEntries(Path loggingPath) throws IOException { } private FileEntry createFileEntry(Path path) { - FileEntry fileEntry = new FileEntry(); + final FileEntry fileEntry = new FileEntry(); fileEntry.setFilename(path.getFileName().toString()); try { fileEntry.setModified(Files.getLastModifiedTime(path)); @@ -61,6 +64,24 @@ else if (isArchive(path)) { @Override public void streamContent(Path folder, String filename, OutputStream stream) throws IOException { - IOUtils.copy(new FileInputStream(Paths.get(folder.toString(), filename).toFile()), stream); + IOUtils.copy(new FileInputStream(getFile(folder, filename)), stream); + } + + private File getFile(Path folder, String filename) { + return Paths.get(folder.toString(), filename).toFile(); + } + + @Override + public void tailContent(Path folder, String filename, OutputStream stream, int lines) throws IOException { + try (ReversedLinesFileReader reader = new ReversedLinesFileReader(getFile(folder, filename))) { + int i = 0; + String line; + List content = new ArrayList<>(); + while ((line = reader.readLine()) != null && i++ <= lines) { + content.add(line); + } + Collections.reverse(content); + IOUtils.writeLines(content, System.lineSeparator(), stream); + } } } diff --git a/lib/src/main/java/eu/hinsch/spring/boot/actuator/logview/LogViewEndpoint.java b/lib/src/main/java/eu/hinsch/spring/boot/actuator/logview/LogViewEndpoint.java index 4df9d10..12e67bb 100644 --- a/lib/src/main/java/eu/hinsch/spring/boot/actuator/logview/LogViewEndpoint.java +++ b/lib/src/main/java/eu/hinsch/spring/boot/actuator/logview/LogViewEndpoint.java @@ -120,10 +120,19 @@ private List sortFiles(List files, SortBy sortBy, boolean } @RequestMapping("/view") - public void view(@RequestParam String filename, @RequestParam(required = false) String base, HttpServletResponse response) throws IOException { + public void view(@RequestParam String filename, + @RequestParam(required = false) String base, + @RequestParam(required = false) Integer tailLines, + HttpServletResponse response) throws IOException { securityCheck(filename); Path path = loggingPath(base); - getFileProvider(path).streamContent(path, filename, response.getOutputStream()); + FileProvider fileProvider = getFileProvider(path); + if (tailLines != null) { + fileProvider.tailContent(path, filename, response.getOutputStream(), tailLines); + } + else { + fileProvider.streamContent(path, filename, response.getOutputStream()); + } } @RequestMapping("/search") diff --git a/lib/src/main/resources/templates/logview.ftl b/lib/src/main/resources/templates/logview.ftl index 077147f..273b60c 100644 --- a/lib/src/main/resources/templates/logview.ftl +++ b/lib/src/main/resources/templates/logview.ftl @@ -62,7 +62,8 @@   <#if file.fileType == 'FILE'> - ${file.filename} + ${file.filename}  + <#if file.fileType == 'ARCHIVE'> ${file.filename} diff --git a/lib/src/test/java/eu/hinsch/spring/boot/actuator/logview/LogViewEndpointTest.java b/lib/src/test/java/eu/hinsch/spring/boot/actuator/logview/LogViewEndpointTest.java index c8d3b82..889c2e3 100644 --- a/lib/src/test/java/eu/hinsch/spring/boot/actuator/logview/LogViewEndpointTest.java +++ b/lib/src/test/java/eu/hinsch/spring/boot/actuator/logview/LogViewEndpointTest.java @@ -222,7 +222,7 @@ public void shouldViewZipFileContent() throws Exception { ByteArrayServletOutputStream outputStream = mockResponseOutputStream(); // when - logViewEndpoint.view("A.log", "file.zip", response); + logViewEndpoint.view("A.log", "file.zip", null, response); // then assertThat(new String(outputStream.toByteArray()), is("content")); @@ -258,7 +258,7 @@ public void shouldViewTarGzFileContent() throws Exception { ByteArrayServletOutputStream outputStream = mockResponseOutputStream(); // when - logViewEndpoint.view("A.log", "file.tar.gz", response); + logViewEndpoint.view("A.log", "file.tar.gz", null, response); // then assertThat(new String(outputStream.toByteArray()), is("content")); @@ -310,7 +310,7 @@ public void shouldNotAllowToListFileOutsideRoot() throws Exception { expectedException.expectMessage(containsString("this String argument must not contain the substring [..]")); // when - logViewEndpoint.view("../somefile", null, null); + logViewEndpoint.view("../somefile", null, null, null); } @Test @@ -320,7 +320,7 @@ public void shouldViewFile() throws Exception { ByteArrayServletOutputStream outputStream = mockResponseOutputStream(); // when - logViewEndpoint.view("file.log", null, response); + logViewEndpoint.view("file.log", null, null, response); // then assertThat(new String(outputStream.toByteArray()), is("abc")); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 8666d9a..5857494 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,3 +1,3 @@ management.context-path=/manage -#logging.path=/tmp/log -endpoints.logview.path=/tmp/log \ No newline at end of file +logging.path=/tmp/log +#endpoints.logview.path=/tmp/log \ No newline at end of file From 4fa10520bc3b740ad9f90f60bd2032e9a5f8944c Mon Sep 17 00:00:00 2001 From: Lukas Hinsch Date: Sat, 28 Mar 2015 15:51:02 +0100 Subject: [PATCH 2/3] changelog + version++ --- CHANGELOG.md | 3 +++ lib/lib.gradle | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50b8a73..9d54c2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # spring-boot-actuator-logview changelog +## 0.2.5 +- allow downloading only the last 50 lines of each file (closes [#13](https://github.com/lukashinsch/spring-boot-actuator-logview/pull/13)) + ## 0.2.4 - show file content when calling endpoint url without trailing slash (fixes [#11](https://github.com/lukashinsch/spring-boot-actuator-logview/issues/11)) - allow to specify logging path via "endpoints.logview.path" property (fixes [#3](https://github.com/lukashinsch/spring-boot-actuator-logview/issues/3) diff --git a/lib/lib.gradle b/lib/lib.gradle index e091771..41243bc 100644 --- a/lib/lib.gradle +++ b/lib/lib.gradle @@ -14,7 +14,7 @@ apply plugin: 'com.github.kt3k.coveralls' ext { springBootVersion = '1.2.1.RELEASE' - libVersion = '0.2.4' + libVersion = '0.2.5' } jar { From f5e66c1bbd37a6a9d60d3ac09593cf6a48b019b8 Mon Sep 17 00:00:00 2001 From: Lukas Hinsch Date: Sat, 28 Mar 2015 16:04:56 +0100 Subject: [PATCH 3/3] Fixed off-by-one error, test-coverage --- .../logview/FileSystemFileProvider.java | 2 +- .../actuator/logview/LogViewEndpointTest.java | 36 +++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/lib/src/main/java/eu/hinsch/spring/boot/actuator/logview/FileSystemFileProvider.java b/lib/src/main/java/eu/hinsch/spring/boot/actuator/logview/FileSystemFileProvider.java index b47e720..3173890 100644 --- a/lib/src/main/java/eu/hinsch/spring/boot/actuator/logview/FileSystemFileProvider.java +++ b/lib/src/main/java/eu/hinsch/spring/boot/actuator/logview/FileSystemFileProvider.java @@ -77,7 +77,7 @@ public void tailContent(Path folder, String filename, OutputStream stream, int l int i = 0; String line; List content = new ArrayList<>(); - while ((line = reader.readLine()) != null && i++ <= lines) { + while ((line = reader.readLine()) != null && i++ < lines) { content.add(line); } Collections.reverse(content); diff --git a/lib/src/test/java/eu/hinsch/spring/boot/actuator/logview/LogViewEndpointTest.java b/lib/src/test/java/eu/hinsch/spring/boot/actuator/logview/LogViewEndpointTest.java index 889c2e3..bd76530 100644 --- a/lib/src/test/java/eu/hinsch/spring/boot/actuator/logview/LogViewEndpointTest.java +++ b/lib/src/test/java/eu/hinsch/spring/boot/actuator/logview/LogViewEndpointTest.java @@ -236,6 +236,17 @@ private void createZipArchive(String archiveFileName, String contentFileName, St } } + @Test(expected = UnsupportedOperationException.class) + public void shouldThrowExceptionWhenCallingTailForZip() throws Exception { + // given + createZipArchive("file.zip", "A.log", "content"); + + // when + logViewEndpoint.view("A.log", "file.zip", 1, response); + + // then -> exception + } + @Test public void shouldListTarGzContent() throws Exception { // given @@ -264,6 +275,17 @@ public void shouldViewTarGzFileContent() throws Exception { assertThat(new String(outputStream.toByteArray()), is("content")); } + @Test(expected = UnsupportedOperationException.class) + public void shouldThrowExceptionWhenCallingTailForTarGz() throws Exception { + // given + createTarGzArchive("file.tar.gz", "A.log", "content"); + + // when + logViewEndpoint.view("A.log", "file.tar.gz", 1, response); + + // then -> exception + } + private void createTarGzArchive(String archiveFileName, String contentFileName, String content) throws Exception { try(TarArchiveOutputStream tos = new TarArchiveOutputStream(new GZIPOutputStream( @@ -326,6 +348,20 @@ public void shouldViewFile() throws Exception { assertThat(new String(outputStream.toByteArray()), is("abc")); } + @Test + public void shouldTailViewOnlyLastLine() throws Exception { + // given + createFile("file.log", "line1" + System.lineSeparator() + "line2" + System.lineSeparator(), now); + ByteArrayServletOutputStream outputStream = mockResponseOutputStream(); + + // when + logViewEndpoint.view("file.log", null, 1, response); + + // then + assertThat(new String(outputStream.toByteArray()), not(containsString("line1"))); + assertThat(new String(outputStream.toByteArray()), containsString("line2")); + } + @Test public void shouldSearchInFiles() throws Exception { // given