Simplify logic for locating default logging config
Fixes gh-1612, Fixes gh-1770
This commit is contained in:
parent
11c1e5ed6b
commit
aec38566ea
|
@ -18,6 +18,7 @@ package org.springframework.boot.logging;
|
|||
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Abstract base class for {@link LoggingSystem} implementations.
|
||||
|
@ -31,12 +32,23 @@ public abstract class AbstractLoggingSystem extends LoggingSystem {
|
|||
|
||||
private final String[] paths;
|
||||
|
||||
public AbstractLoggingSystem(ClassLoader classLoader, boolean fileOutput, boolean consoleOutput) {
|
||||
this.classLoader = classLoader;
|
||||
this.paths = getLogFileName(fileOutput, consoleOutput);
|
||||
private boolean fileOutput;
|
||||
|
||||
private boolean consoleOutput;
|
||||
|
||||
public AbstractLoggingSystem(ClassLoader classLoader) {
|
||||
this(classLoader, false, true);
|
||||
}
|
||||
|
||||
protected abstract String[] getLogFileName(boolean fileOutput, boolean consoleOutput);
|
||||
public AbstractLoggingSystem(ClassLoader classLoader, boolean fileOutput,
|
||||
boolean consoleOutput) {
|
||||
this.classLoader = classLoader;
|
||||
this.fileOutput = fileOutput;
|
||||
this.consoleOutput = consoleOutput;
|
||||
this.paths = getLogFileNames();
|
||||
}
|
||||
|
||||
protected abstract String[] getLogFileNames();
|
||||
|
||||
protected final ClassLoader getClassLoader() {
|
||||
return this.classLoader;
|
||||
|
@ -56,14 +68,12 @@ public abstract class AbstractLoggingSystem extends LoggingSystem {
|
|||
return;
|
||||
}
|
||||
}
|
||||
// Fallback to the non-prefixed value
|
||||
initialize(getPackagedConfigFile(this.paths[this.paths.length - 1]));
|
||||
// Fallback to the non-prefixed value taking into account file and console preferences
|
||||
initialize(getPackagedConfigFile(addChannels(this.paths[this.paths.length - 1])));
|
||||
}
|
||||
|
||||
protected void initializeWithSensibleDefaults() {
|
||||
String path = this.paths[this.paths.length - 1];
|
||||
path = path.replaceAll("-console", "").replaceAll("-file", "");
|
||||
initialize(getPackagedConfigFile("basic-" + path));
|
||||
initialize(getPackagedConfigFile("basic-" + this.paths[this.paths.length - 1]));
|
||||
}
|
||||
|
||||
protected final String getPackagedConfigFile(String fileName) {
|
||||
|
@ -74,4 +84,14 @@ public abstract class AbstractLoggingSystem extends LoggingSystem {
|
|||
return defaultPath;
|
||||
}
|
||||
|
||||
private String addChannels(String fileName) {
|
||||
String extension = "." + StringUtils.getFilenameExtension(fileName);
|
||||
return fileName.replace(extension, getChannel() + extension);
|
||||
}
|
||||
|
||||
private String getChannel() {
|
||||
return (fileOutput && consoleOutput) ? "-file-console" : (fileOutput ? "-file"
|
||||
: (consoleOutput ? "" : "-none"));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -51,27 +51,19 @@ public class JavaLoggingSystem extends AbstractLoggingSystem {
|
|||
LEVELS = Collections.unmodifiableMap(levels);
|
||||
}
|
||||
|
||||
public JavaLoggingSystem(ClassLoader classLoader, boolean fileOutput, boolean consoleOutput) {
|
||||
super(classLoader, fileOutput, consoleOutput);
|
||||
public JavaLoggingSystem(ClassLoader classLoader) {
|
||||
this(classLoader, false, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] getLogFileName(boolean fileOutput, boolean consoleOutput) {
|
||||
if (fileOutput && consoleOutput) {
|
||||
return new String[] { "logging-file-console.properties" };
|
||||
}
|
||||
else if (fileOutput) {
|
||||
return new String[] { "logging-file.properties" };
|
||||
}
|
||||
else if (consoleOutput) {
|
||||
return new String[] { "logging-console.properties" };
|
||||
}
|
||||
else {
|
||||
return new String[] { "logging.properties" };
|
||||
}
|
||||
public JavaLoggingSystem(ClassLoader classLoader, boolean fileOutput,
|
||||
boolean consoleOutput) {
|
||||
super(classLoader, fileOutput, consoleOutput);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected String[] getLogFileNames() {
|
||||
return new String[] { "logging.properties" };
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(String configLocation) {
|
||||
|
|
|
@ -51,24 +51,18 @@ public class Log4JLoggingSystem extends Slf4JLoggingSystem {
|
|||
LEVELS = Collections.unmodifiableMap(levels);
|
||||
}
|
||||
|
||||
public Log4JLoggingSystem(ClassLoader classLoader, boolean fileOutput, boolean consoleOutput) {
|
||||
super(classLoader, fileOutput, consoleOutput);
|
||||
public Log4JLoggingSystem(ClassLoader classLoader) {
|
||||
this(classLoader, false, true);
|
||||
}
|
||||
|
||||
public Log4JLoggingSystem(ClassLoader classLoader, boolean fileOutput,
|
||||
boolean consoleOutput) {
|
||||
super(classLoader, fileOutput, consoleOutput);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] getLogFileName(boolean fileOutput, boolean consoleOutput) {
|
||||
if (fileOutput && consoleOutput) {
|
||||
return new String[] { "log4j-file-console.xml", "log4j-file-console.properties" };
|
||||
}
|
||||
else if (fileOutput) {
|
||||
return new String[] { "log4j-file.xml", "log4j-file.properties" };
|
||||
}
|
||||
else if (consoleOutput) {
|
||||
return new String[] { "log4j-console.xml", "log4j-console.properties" };
|
||||
}
|
||||
else {
|
||||
return new String[] { "log4j.xml", "log4j.properties" };
|
||||
}
|
||||
protected String[] getLogFileNames() {
|
||||
return new String[] { "log4j.xml", "log4j.properties" };
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -27,7 +27,6 @@ import org.apache.logging.log4j.core.LoggerContext;
|
|||
import org.apache.logging.log4j.core.config.Configuration;
|
||||
import org.apache.logging.log4j.core.config.ConfigurationFactory;
|
||||
import org.apache.logging.log4j.core.config.ConfigurationSource;
|
||||
|
||||
import org.springframework.boot.logging.LogLevel;
|
||||
import org.springframework.boot.logging.LoggingSystem;
|
||||
import org.springframework.boot.logging.Slf4JLoggingSystem;
|
||||
|
@ -57,24 +56,17 @@ public class Log4J2LoggingSystem extends Slf4JLoggingSystem {
|
|||
LEVELS = Collections.unmodifiableMap(levels);
|
||||
}
|
||||
|
||||
public Log4J2LoggingSystem(ClassLoader classLoader) {
|
||||
this(classLoader, false, true);
|
||||
}
|
||||
|
||||
public Log4J2LoggingSystem(ClassLoader classLoader, boolean fileOutput, boolean consoleOutput) {
|
||||
super(classLoader, fileOutput, consoleOutput);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] getLogFileName(boolean fileOutput, boolean consoleOutput) {
|
||||
if (fileOutput && consoleOutput) {
|
||||
return new String[] { "log4j2-file-console.json", "log4j2-file-console.jsn", "log4j2-file-console.xml" };
|
||||
}
|
||||
else if (fileOutput) {
|
||||
return new String[] { "log4j2-file.json", "log4j2-file.jsn", "log4j2-file.xml" };
|
||||
}
|
||||
else if (consoleOutput) {
|
||||
return new String[] { "log4j2-console.json", "log4j2-console.jsn", "log4j2-console.xml" };
|
||||
}
|
||||
else {
|
||||
return new String[] { "log4j2.json", "log4j2.jsn", "log4j2.xml" };
|
||||
}
|
||||
protected String[] getLogFileNames() {
|
||||
return new String[] { "log4j2.json", "log4j2.jsn", "log4j2.xml" };
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -58,27 +58,19 @@ public class LogbackLoggingSystem extends Slf4JLoggingSystem {
|
|||
LEVELS = Collections.unmodifiableMap(levels);
|
||||
}
|
||||
|
||||
public LogbackLoggingSystem(ClassLoader classLoader, boolean fileOutput, boolean consoleOutput) {
|
||||
super(classLoader, fileOutput, consoleOutput);
|
||||
public LogbackLoggingSystem(ClassLoader classLoader) {
|
||||
this(classLoader, false, true);
|
||||
}
|
||||
|
||||
public LogbackLoggingSystem(ClassLoader classLoader, boolean fileOutput,
|
||||
boolean consoleOutput) {
|
||||
super(classLoader, fileOutput, consoleOutput);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] getLogFileName(boolean fileOutput, boolean consoleOutput) {
|
||||
if (fileOutput && consoleOutput) {
|
||||
return new String[] { "logback-test-file-console.groovy", "logback-test-file-console.xml",
|
||||
"logback-file-console.groovy", "logback-file-console.xml" };
|
||||
}
|
||||
else if (fileOutput) {
|
||||
return new String[] { "logback-test-file.groovy", "logback-test-file.xml", "logback-file.groovy",
|
||||
"logback-file.xml" };
|
||||
}
|
||||
else if (consoleOutput) {
|
||||
return new String[] { "logback-test-console.groovy", "logback-test-console.xml", "logback-console.groovy",
|
||||
"logback-console.xml" };
|
||||
}
|
||||
else {
|
||||
return new String[] { "logback-test.groovy", "logback-test.xml", "logback.groovy", "logback.xml" };
|
||||
}
|
||||
protected String[] getLogFileNames() {
|
||||
return new String[] { "logback-test.groovy", "logback-test.xml",
|
||||
"logback.groovy", "logback.xml" };
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
handlers =java.util.logging.ConsoleHandler
|
||||
handlers =
|
||||
.level = INFO
|
||||
|
||||
java.util.logging.ConsoleHandler.formatter = org.springframework.boot.logging.java.SimpleFormatter
|
||||
java.util.logging.ConsoleHandler.level = ALL
|
||||
|
||||
org.hibernate.validator.internal.util.Version.level = WARNING
|
||||
org.apache.coyote.http11.Http11NioProtocol.level = WARNING
|
||||
org.crsh.plugin.level = WARNING
|
|
@ -1,6 +1,9 @@
|
|||
handlers =
|
||||
handlers =java.util.logging.ConsoleHandler
|
||||
.level = INFO
|
||||
|
||||
java.util.logging.ConsoleHandler.formatter = org.springframework.boot.logging.java.SimpleFormatter
|
||||
java.util.logging.ConsoleHandler.level = ALL
|
||||
|
||||
org.hibernate.validator.internal.util.Version.level = WARNING
|
||||
org.apache.coyote.http11.Http11NioProtocol.level = WARNING
|
||||
org.crsh.plugin.level = WARNING
|
||||
|
|
|
@ -1,15 +1,10 @@
|
|||
log4j.rootCategory=INFO, CONSOLE
|
||||
log4j.rootCategory=INFO
|
||||
|
||||
PID=????
|
||||
LOG_PATH=${java.io.tmpdir}
|
||||
LOG_FILE=${LOG_PATH}/spring.log
|
||||
LOG_PATTERN=[%d{yyyy-MM-dd HH:mm:ss.SSS}] boot%X{context} - ${PID} %5p [%t] --- %c{1}: %m%n
|
||||
|
||||
# CONSOLE is set to be a ConsoleAppender using a PatternLayout.
|
||||
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
|
||||
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
|
||||
log4j.appender.CONSOLE.layout.ConversionPattern=${LOG_PATTERN}
|
||||
|
||||
|
||||
log4j.category.org.hibernate.validator.internal.util.Version=WARN
|
||||
log4j.category.org.apache.coyote.http11.Http11NioProtocol=WARN
|
|
@ -1,10 +1,15 @@
|
|||
log4j.rootCategory=INFO
|
||||
log4j.rootCategory=INFO, CONSOLE
|
||||
|
||||
PID=????
|
||||
LOG_PATH=${java.io.tmpdir}
|
||||
LOG_FILE=${LOG_PATH}/spring.log
|
||||
LOG_PATTERN=[%d{yyyy-MM-dd HH:mm:ss.SSS}] boot%X{context} - ${PID} %5p [%t] --- %c{1}: %m%n
|
||||
|
||||
# CONSOLE is set to be a ConsoleAppender using a PatternLayout.
|
||||
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
|
||||
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
|
||||
log4j.appender.CONSOLE.layout.ConversionPattern=${LOG_PATTERN}
|
||||
|
||||
|
||||
log4j.category.org.hibernate.validator.internal.util.Version=WARN
|
||||
log4j.category.org.apache.coyote.http11.Http11NioProtocol=WARN
|
||||
|
|
|
@ -6,11 +6,6 @@
|
|||
<Property name="LOG_FILE">${sys:LOG_PATH}/spring.log</Property>
|
||||
<Property name="LOG_PATTERN">[%d{yyyy-MM-dd HH:mm:ss.SSS}] boot%X{context} - ${sys:PID} %5p [%t] --- %c{1}: %m%n</Property>
|
||||
</Properties>
|
||||
<Appenders>
|
||||
<Console name="Console" target="SYSTEM_OUT" follow="true">
|
||||
<PatternLayout pattern="${LOG_PATTERN}"/>
|
||||
</Console>
|
||||
</Appenders>
|
||||
<Loggers>
|
||||
<Logger name="org.hibernate.validator.internal.util.Version" level="warn" />
|
||||
<Logger name="org.apache.coyote.http11.Http11NioProtocol" level="warn" />
|
||||
|
@ -21,7 +16,6 @@
|
|||
<Logger name="org.eclipse.jetty.util.component.AbstractLifeCycle" level="error" />
|
||||
|
||||
<Root level="info">
|
||||
<AppenderRef ref="Console"/>
|
||||
</Root>
|
||||
</Loggers>
|
||||
</Configuration>
|
|
@ -6,6 +6,11 @@
|
|||
<Property name="LOG_FILE">${sys:LOG_PATH}/spring.log</Property>
|
||||
<Property name="LOG_PATTERN">[%d{yyyy-MM-dd HH:mm:ss.SSS}] boot%X{context} - ${sys:PID} %5p [%t] --- %c{1}: %m%n</Property>
|
||||
</Properties>
|
||||
<Appenders>
|
||||
<Console name="Console" target="SYSTEM_OUT" follow="true">
|
||||
<PatternLayout pattern="${LOG_PATTERN}"/>
|
||||
</Console>
|
||||
</Appenders>
|
||||
<Loggers>
|
||||
<Logger name="org.hibernate.validator.internal.util.Version" level="warn" />
|
||||
<Logger name="org.apache.coyote.http11.Http11NioProtocol" level="warn" />
|
||||
|
@ -16,6 +21,7 @@
|
|||
<Logger name="org.eclipse.jetty.util.component.AbstractLifeCycle" level="error" />
|
||||
|
||||
<Root level="info">
|
||||
<AppenderRef ref="Console"/>
|
||||
</Root>
|
||||
</Loggers>
|
||||
</Configuration>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="UTF8"?>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<included>
|
||||
<conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
|
||||
<property name="LOG_FILE" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}/}spring.log}"/>
|
|
@ -1,10 +1,17 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<included>
|
||||
|
||||
<include resource="org/springframework/boot/logging/logback/basic.xml"/>
|
||||
|
||||
|
||||
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
|
||||
<conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
|
||||
|
||||
<property name="CONSOLE_LOG_PATTERN" value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t{14}]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wex"/>
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
|
||||
<charset>utf8</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="INFO">
|
||||
</root>
|
||||
|
||||
<appender-ref ref="CONSOLE" />
|
||||
</root>
|
||||
</included>
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF8"?>
|
||||
<included>
|
||||
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
|
||||
<conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
|
||||
|
||||
<property name="CONSOLE_LOG_PATTERN" value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t{14}]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wex"/>
|
||||
<include resource="org/springframework/boot/logging/logback/base.xml"/>
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
|
||||
<charset>utf8</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="CONSOLE" />
|
||||
</root>
|
||||
</included>
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<include resource="org/springframework/boot/logging/logback/basic.xml" />
|
||||
<include resource="org/springframework/boot/logging/logback/base.xml" />
|
||||
</configuration>
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<included>
|
||||
|
||||
<appender name="DEBUG_LEVEL_REMAPPER" class="org.springframework.boot.logging.logback.LevelRemappingAppender">
|
||||
<destinationLogger>org.springframework.boot</destinationLogger>
|
||||
</appender>
|
||||
|
||||
|
||||
<logger name="org.hibernate.validator.internal.util.Version" level="WARN"/>
|
||||
<logger name="org.apache.coyote.http11.Http11NioProtocol" level="WARN"/>
|
||||
<logger name="org.crsh.plugin" level="WARN"/>
|
||||
<logger name="org.apache.tomcat.util.net.NioSelectorPool" level="WARN"/>
|
||||
<logger name="org.apache.catalina.startup.DigesterFactory" level="ERROR"/>
|
||||
<logger name="org.apache.catalina.util.LifecycleBase" level="ERROR"/>
|
||||
<logger name="org.eclipse.jetty.util.component.AbstractLifeCycle" level="ERROR"/>
|
||||
<logger name="org.thymeleaf" additivity="false">
|
||||
<appender-ref ref="DEBUG_LEVEL_REMAPPER"/>
|
||||
</logger>
|
||||
|
||||
</included>
|
|
@ -1,5 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF8"?>
|
||||
<configuration>
|
||||
<include resource="org/springframework/boot/logging/logback/base.xml"/>
|
||||
<include resource="org/springframework/boot/logging/logback/basic-console.xml"/>
|
||||
</configuration>
|
|
@ -1,6 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF8"?>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<include resource="org/springframework/boot/logging/logback/base.xml"/>
|
||||
<include resource="org/springframework/boot/logging/logback/basic-file.xml"/>
|
||||
<include resource="org/springframework/boot/logging/logback/basic-console.xml"/>
|
||||
<include resource="org/springframework/boot/logging/logback/base-file.xml"/>
|
||||
<include resource="org/springframework/boot/logging/logback/base.xml"/>
|
||||
</configuration>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
<?xml version="1.0" encoding="UTF8"?>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<include resource="org/springframework/boot/logging/logback/base.xml"/>
|
||||
<include resource="org/springframework/boot/logging/logback/basic-file.xml"/>
|
||||
<include resource="org/springframework/boot/logging/logback/base-file.xml"/>
|
||||
</configuration>
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<root level="INFO">
|
||||
</root>
|
||||
</configuration>
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="UTF8"?>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<include resource="org/springframework/boot/logging/logback/base.xml"/>
|
||||
</configuration>
|
||||
|
|
|
@ -103,7 +103,18 @@ public class LoggingApplicationListenerTests {
|
|||
String output = this.outputCapture.toString().trim();
|
||||
assertTrue("Wrong output:\n" + output, output.contains("Hello world"));
|
||||
assertFalse("Wrong output:\n" + output, output.contains("???"));
|
||||
assertTrue(new File(tmpDir() + "/spring.log").exists());
|
||||
assertFalse(new File(tmpDir() + "/spring.log").exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noConsole() {
|
||||
EnvironmentTestUtils.addEnvironment(this.context, "logging.console: false");
|
||||
this.initializer.initialize(this.context.getEnvironment(),
|
||||
this.context.getClassLoader());
|
||||
this.logger.info("Hello world");
|
||||
String output = this.outputCapture.toString().trim();
|
||||
assertFalse("Wrong output:\n" + output, output.contains("Hello world"));
|
||||
assertFalse(new File(tmpDir() + "/spring.log").exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -130,7 +141,7 @@ public class LoggingApplicationListenerTests {
|
|||
String output = this.outputCapture.toString().trim();
|
||||
assertTrue("Wrong output:\n" + output, output.contains("Hello world"));
|
||||
assertFalse("Wrong output:\n" + output, output.contains("???"));
|
||||
assertTrue(new File(tmpDir() + "/spring.log").exists());
|
||||
assertFalse(new File(tmpDir() + "/spring.log").exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
Loading…
Reference in New Issue