diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/system/ApplicationPidFileWriter.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/system/ApplicationPidFileWriter.java index 6c69c4661e8..e743bdb1ecd 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/system/ApplicationPidFileWriter.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/system/ApplicationPidFileWriter.java @@ -74,7 +74,7 @@ public class ApplicationPidFileWriter implements */ public ApplicationPidFileWriter(File file) { Assert.notNull(file, "File must not be null"); - String override = getOverride(); + String override = SystemProperties.get(PROPERTY_VARIABLES); if (override != null) { this.file = new File(override); } @@ -83,23 +83,6 @@ public class ApplicationPidFileWriter implements } } - private String getOverride() { - for (String property : PROPERTY_VARIABLES) { - try { - String override = System.getProperty(property); - override = (override != null ? override : System.getenv(property)); - if (override != null) { - return override; - } - } - catch (Throwable ex) { - System.err.println("Could not resolve '" + property - + "' as system property: " + ex); - } - } - return null; - } - @Override public void onApplicationEvent(ApplicationStartedEvent event) { if (created.compareAndSet(false, true)) { diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/system/EmbeddedServerPortFileWriter.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/system/EmbeddedServerPortFileWriter.java new file mode 100644 index 00000000000..2bf26637e63 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/system/EmbeddedServerPortFileWriter.java @@ -0,0 +1,139 @@ +/* + * Copyright 2010-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 org.springframework.boot.actuate.system; + +import java.io.File; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.boot.context.embedded.EmbeddedServletContainerInitializedEvent; +import org.springframework.boot.context.embedded.EmbeddedWebApplicationContext; +import org.springframework.context.ApplicationListener; +import org.springframework.util.Assert; +import org.springframework.util.FileCopyUtils; +import org.springframework.util.StringUtils; + +/** + * An {@link ApplicationListener} that saves embedded server port and management port into + * file. This application listener will be triggered whenever the servlet container + * starts, and the file name can be overridden at runtime with a System property or + * environment variable named "PORTFILE" or "portfile". + * + * @author David Liu + * @author Phillip Webb + * @since 1.2.0 + */ +public class EmbeddedServerPortFileWriter implements + ApplicationListener { + + private static final String DEFAULT_FILE_NAME = "application.port"; + + private static final String[] PROPERTY_VARIABLES = { "PORTFILE", "portfile" }; + + private static final Log logger = LogFactory.getLog(ApplicationPidFileWriter.class); + + private final File file; + + /** + * Create a new {@link ApplicationPidFileWriter} instance using the filename + * 'application.pid'. + */ + public EmbeddedServerPortFileWriter() { + this.file = new File(DEFAULT_FILE_NAME); + } + + /** + * Create a new {@link ApplicationPidFileWriter} instance with a specified filename. + * @param filename the name of file containing pid + */ + public EmbeddedServerPortFileWriter(String filename) { + this(new File(filename)); + } + + /** + * Create a new {@link ApplicationPidFileWriter} instance with a specified file. + * @param file the file containing pid + */ + public EmbeddedServerPortFileWriter(File file) { + Assert.notNull(file, "File must not be null"); + String override = SystemProperties.get(PROPERTY_VARIABLES); + if (override != null) { + this.file = new File(override); + } + else { + this.file = file; + } + } + + @Override + public void onApplicationEvent(EmbeddedServletContainerInitializedEvent event) { + File portFile = getPortFile(event.getApplicationContext()); + try { + String port = String.valueOf(event.getEmbeddedServletContainer().getPort()); + createParentFolder(portFile); + FileCopyUtils.copy(port.getBytes(), portFile); + portFile.deleteOnExit(); + } + catch (Exception ex) { + logger.warn(String.format("Cannot create pid file %s", this.file)); + } + } + + /** + * Return the actual port file that should be written for the given application + * context. The default implementation builds a file from the source file and the + * application context namespace. + * @param applicationContext the source application context + * @return the file that should be written + */ + protected File getPortFile(EmbeddedWebApplicationContext applicationContext) { + String contextName = applicationContext.getNamespace(); + if (StringUtils.isEmpty(contextName)) { + return this.file; + } + String name = this.file.getName(); + String extension = StringUtils.getFilenameExtension(this.file.getName()); + name = name.substring(0, name.length() - extension.length() - 1); + if (isUpperCase(name)) { + name = name + "-" + contextName.toUpperCase(); + } + else { + name = name + "-" + contextName.toLowerCase(); + } + if (StringUtils.hasLength(extension)) { + name = name + "." + extension; + } + return new File(this.file.getParentFile(), name); + } + + private boolean isUpperCase(String name) { + for (int i = 0; i < name.length(); i++) { + if (!Character.isUpperCase(name.charAt(i))) { + return false; + } + } + return true; + } + + private void createParentFolder(File file) { + File parent = file.getParentFile(); + if (parent != null) { + parent.mkdirs(); + } + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/system/SystemProperties.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/system/SystemProperties.java new file mode 100644 index 00000000000..63d1fb24ef0 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/system/SystemProperties.java @@ -0,0 +1,41 @@ +/* + * Copyright 2012-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 org.springframework.boot.actuate.system; + +/** + * @author Phillip Webb + */ +class SystemProperties { + + public static String get(String... properties) { + for (String property : properties) { + try { + String override = System.getProperty(property); + override = (override != null ? override : System.getenv(property)); + if (override != null) { + return override; + } + } + catch (Throwable ex) { + System.err.println("Could not resolve '" + property + + "' as system property: " + ex); + } + } + return null; + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/system/EmbeddedServerPortFileWriterTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/system/EmbeddedServerPortFileWriterTests.java new file mode 100644 index 00000000000..31f9455e9a4 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/system/EmbeddedServerPortFileWriterTests.java @@ -0,0 +1,115 @@ +/* + * Copyright 2010-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 org.springframework.boot.actuate.system; + +import java.io.File; +import java.io.FileReader; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.springframework.boot.context.embedded.EmbeddedServletContainer; +import org.springframework.boot.context.embedded.EmbeddedServletContainerInitializedEvent; +import org.springframework.boot.context.embedded.EmbeddedWebApplicationContext; +import org.springframework.util.FileCopyUtils; +import org.springframework.util.StringUtils; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Tests {@link EmbeddedServerPortFileWriter}. + * + * @author David Liu + * @author Phillip Webb + */ +public class EmbeddedServerPortFileWriterTests { + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @Before + @After + public void reset() { + System.clearProperty("PORTFILE"); + } + + @Test + public void createPortFile() throws Exception { + File file = this.temporaryFolder.newFile(); + EmbeddedServerPortFileWriter listener = new EmbeddedServerPortFileWriter(file); + listener.onApplicationEvent(mockEvent("", 8080)); + assertThat(FileCopyUtils.copyToString(new FileReader(file)), equalTo("8080")); + } + + @Test + public void overridePortFile() throws Exception { + File file = this.temporaryFolder.newFile(); + System.setProperty("PORTFILE", this.temporaryFolder.newFile().getAbsolutePath()); + EmbeddedServerPortFileWriter listener = new EmbeddedServerPortFileWriter(file); + listener.onApplicationEvent(mockEvent("", 8080)); + assertThat(FileCopyUtils.copyToString(new FileReader(System + .getProperty("PORTFILE"))), equalTo("8080")); + } + + @Test + public void createManagementPortFile() throws Exception { + File file = this.temporaryFolder.newFile(); + EmbeddedServerPortFileWriter listener = new EmbeddedServerPortFileWriter(file); + listener.onApplicationEvent(mockEvent("", 8080)); + listener.onApplicationEvent(mockEvent("management", 9090)); + assertThat(FileCopyUtils.copyToString(new FileReader(file)), equalTo("8080")); + String managementFile = file.getName(); + managementFile = managementFile.substring(0, managementFile.length() + - StringUtils.getFilenameExtension(managementFile).length() - 1); + managementFile = managementFile + "-management." + + StringUtils.getFilenameExtension(file.getName()); + assertThat(FileCopyUtils.copyToString(new FileReader(new File(file + .getParentFile(), managementFile))), equalTo("9090")); + } + + @Test + public void createUpperCaseManagementPortFile() throws Exception { + File file = this.temporaryFolder.newFile(); + file = new File(file.getParentFile(), file.getName().toUpperCase()); + EmbeddedServerPortFileWriter listener = new EmbeddedServerPortFileWriter(file); + listener.onApplicationEvent(mockEvent("management", 9090)); + String managementFile = file.getName(); + managementFile = managementFile.substring(0, managementFile.length() + - StringUtils.getFilenameExtension(managementFile).length() - 1); + managementFile = managementFile + "-MANAGEMENT." + + StringUtils.getFilenameExtension(file.getName()); + assertThat(FileCopyUtils.copyToString(new FileReader(new File(file + .getParentFile(), managementFile))), equalTo("9090")); + + } + + private EmbeddedServletContainerInitializedEvent mockEvent(String name, int port) { + EmbeddedWebApplicationContext applicationContext = mock(EmbeddedWebApplicationContext.class); + EmbeddedServletContainer source = mock(EmbeddedServletContainer.class); + given(applicationContext.getNamespace()).willReturn(name); + given(source.getPort()).willReturn(port); + EmbeddedServletContainerInitializedEvent event = new EmbeddedServletContainerInitializedEvent( + applicationContext, source); + return event; + } + +} diff --git a/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc b/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc index ad3a66a0311..c0a214bacbf 100644 --- a/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc @@ -856,30 +856,38 @@ if needed. [[production-ready-process-monitoring]] == Process monitoring -In Spring Boot Actuator you can find `ApplicationPidFileWriter` which creates file -containing application PID (by default in application directory and file name is -`application.pid`). It's not activated by default, but you can do it in two simple -ways described below. +In Spring Boot Actuator you can find a couple of classes to create files that are useful +for process monitoring: + +* `ApplicationPidFileWriter` creates a file containing the application PID (by default in + the application directory with the file name `application.pid`). +* `EmbeddedServerPortFileWriter` creates a file (or files) containing the ports of the + embedded server (by default in the application directory with the file name + `application.port`). + +These writers are not activated by default, but you can enable them in one of the ways +described below. [[production-ready-process-monitoring-configuration]] === Extend configuration -In `META-INF/spring.factories` file you have to activate the listener: +In `META-INF/spring.factories` file you have to activate the listener(s): [indent=0] ---- org.springframework.context.ApplicationListener=\ - org.springframework.boot.actuate.system.ApplicationPidFileWriter + org.springframework.boot.actuate.system.ApplicationPidFileWriter, + org.springframework.boot.actuate.system.EmbeddedServerPortFileWriter ---- [[production-ready-process-monitoring-programmatically]] === Programmatically -You can also activate this listener by invoking `SpringApplication.addListeners(...)` -method and passing `ApplicationPidFileWriter` object. You can also customize file name -and path through constructor. +You can also activate a listener by invoking the `SpringApplication.addListeners(...)` +method and passing the appropriate `Writer` object. This method also allows you to +customize file name and path via the `Writer` constructor.