How to get Environment properties from application.properties into logback.groovy in Spring Boot project? - spring

Trying to inject properties defined in application.properties/application.yml into logback.groovy script in Spring Boot project.
I cannot Inject Environment or ApplicationContext into groovy scripts.
Are there any workarounds?
I am not looking for solutions like System.getProperty('spring.profiles.active')
src/main/resources/logback.groovy
import org.springframework.core.env.Environment
#Inject private Environment env; //this is not working. how to get env here?
println "spring.profiles.active : ${env.getProperty('spring.profiles.active')}"
appender("STDOUT", ConsoleAppender) {
encoder(PatternLayoutEncoder) {
pattern = "%green(%d{HH:mm:ss.SSS}) [%thread] %highlight(%-5level) %cyan(%logger{36}) - %msg%n"
}
}
if(System.getProperty("spring.profiles.active")?.equalsIgnoreCase("prod")) {
root INFO, ["STDOUT", "FILE"]
} else {
root INFO, ["STDOUT"]
}
src/main/resources/application.yml
---
spring:
profiles:
active: development

logback.groovy needs to be evaluated very early because otherwise the code for loading the spring configuration, instantiating beans, etc. could not log anything. That's why #Inject can't work.
I see 2 options:
You could easily load application.properties in logback.groovy, but it's far from trivial to take all the configuration override mechanisms that spring provides into account
Create a spring bean that changes the logback configuration programmaticaly
when initialized
A different approach is to externalize the logback configuration on production with -Dlogback.configurationFile=/path/to/config.groovy. Putting the config file in a well known location (like /etc/my-app) makes it easy to change log levels in production without re-deploying (or even re-starting).

Sorry to resurrect this thread but while since this is thread is what I found while looking for a solution, I wanted to share a workaround.
I have used a Custom converter and conversion rule to pull out properties in classpath resource (i.e. application.properties):
In logback.groovy:
conversionRule('springApplicationName', CustomSpringApplicationNameConverter)
def patternExpression = '%green([%d{YYYY-MM-dd HH:mm:ss.SSS}]) [%t] %highlight(%-5level) %magenta([%springApplicationName]) - %cyan(%logger{36}) -- %msg%n'
and then 'patternExpression' used in desired appender
and my custom converter class (in groovy):
class CustomSpringApplicationNameConverter extends ClassicConverter {
#Override
String convert(ILoggingEvent event) {
ClassPathResource classPathResource = new ClassPathResource('application.properties')
Properties applicationProperties = new Properties()
applicationProperties.load(classPathResource.inputStream)
String springApplicationName = applicationProperties.getProperty('spring.application.name')
if (!springApplicationName) {
System.err.println('Could not find entry for \'spring.application.name\' in \'application.properties\'')
}
springApplicationName
}
}

Related

How to exclude #ConfigurationProperties from reloading in kubernetes configMap

In my application I have ds bean with prefix so I can defined it in application.properties by profile
#Bean
#ConfigurationProperties(prefix = "atomikos.db")
public AbstractDataSourceBean dbDataSource() {
AtomikosNonXADataSourceBean atomikosDataSource = new AtomikosNonXADataSourceBean();
return atomikosDataSource;
}
according this article this bean will be reload when configMap changed but how I can exclude it and still use application.properties to define properties this bean according to profile ? In production system I just can not recreate connection to db
According to the latest documentation, you should set
spring.cloud.refresh.never-refreshable=my.package.ClassName
where my.package.ClassName is the type of bean you don't want refreshed.

How to disable SpringBoot autoconfiguration for TomcatServletWebServerFactory in order for a custom spring-starter to provide it?

so I was writing my own SpringBootStarter which was supposed to enable the JNDI lookup in the embedded tomcat of a SpringBoot application.
My sample SpringBoot application has a dependency of my custom SpringBootStarter, which in turn has a dependency on the spring-boot-starter-web. If I create a Configuration class like the following inside the sample SpringBoot application everything works perfectly:
#Configuration
public class SampleSpringBootAppConfig {
#Bean
public TomcatServletWebServerFactory tomcatFactory() {
return new TomcatServletWebServerFactory() {
#Override
protected TomcatWebServer getTomcatWebServer(org.apache.catalina.startup.Tomcat tomcat) {
System.out.println("CONFIGURING CUSTOM TOMCAT WEB SERVER FACTORY ");
tomcat.enableNaming();
return super.getTomcatWebServer(tomcat);
}
#Override
protected void postProcessContext(Context context) {
ContextResource resource = new ContextResource();
resource.setName("myDataSource");
resource.setType(DataSource.class.getName());
resource.setProperty("driverClassName", "org.postgresql.Driver");
resource.setProperty("url", "jdbc:postgresql://localhost:5432/postgres");
resource.setProperty("username", "postgres");
resource.setProperty("password", "postgres");
context.getNamingResources()
.addResource(resource);
}
};
}
Because SpringBoot finds a custom Bean, there won't be an autoconfigured default one / it is overridden and the JNDI is successfully enabled.
However, as soon as I extract this Bean configuration into my auto-configure module of my custom SpringBoot Starter, the following exception is thrown while trying to start the sample SpringBoot application:
org.springframework.context.ApplicationContextException: Unable to start web server; nested exception is org.springframework.context.ApplicationContextException: Unable to start ServletWebServerApplicationContext due to multiple ServletWebServerFactory beans : tomcatServletWebServerFactory,tomcatFactory
I reckon this is due to SpringBoot not finding a customized Bean and therefore creating an autoconfigured default one which also won't be overridden. So now there will be two ServletWebServerFactory beans, the default one and the one from my auto- configure module.
What i tried so far (to no avail) :
annotating my custom Bean with #Primary
setting spring.main.allow-bean-definition-overriding to true
Is there any way to make SpringBoot not initialize a autoconfigured default bean, or any other possible solution to this?
try this
#AutoConfigureBefore(ServletWebServerFactoryAutoConfiguration.class)
I was able to solve this myself by excluding the responsible AutoConfiguration class:
#SpringBootApplication ( exclude = ServletWebServerFactoryAutoConfiguration.class)

Add Spring Boot Profile to Sleuth/Zipkin logs

I'm using these dependencies:
compile 'org.springframework.cloud:spring-cloud-starter-zipkin'
compile 'org.springframework.cloud:spring-cloud-starter-sleuth'
compile 'org.springframework.cloud:spring-cloud-sleuth-zipkin'
Is there a possibility to add the current active profile(s) to each log line? This would make it possible to filter logs based on the profiles in Splunk/ELK/...
So instead of
2017-03-13 13:38:30.465 INFO [app,,,] 19220 --- [ main] com.company.app.Application : Started Application in 20.682 seconds (JVM running for 22.166)
it should log
2017-03-13 13:38:30.465 INFO [app,,,] [dev] 19220 --- [ main] com.company.app.Application : Started Application in 20.682 seconds (JVM running for 22.166)
EDIT:
Based on Marcin's answer, I implemented it as follows:
application.yml
logging:
pattern:
level: "%X{profiles} %5p"
ProfileLogger.java
public class ProfileLogger implements SpanLogger {
private final Environment environment;
private final Logger log;
private final Pattern nameSkipPattern;
#Autowired
public ProfileLogger(String nameSkipPattern, final Environment environment) {
this.nameSkipPattern = Pattern.compile(nameSkipPattern);
this.environment = environment;
this.log = org.slf4j.LoggerFactory.getLogger(this.getClass());
}
private void setProfiles() {
MDC.put("profiles", Arrays.toString(environment.getActiveProfiles()));
}
#Override
public void logStartedSpan(Span parent, Span span) {
setProfiles();
...
}
... // (as https://github.com/spring-cloud/spring-cloud-sleuth/blob/master/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/Slf4jSpanLogger.java)
}
LogConfig.java
#Configuration
public class LogConfig {
private final Environment environment;
#Autowired
public LogConfig(final Environment environment) {
this.environment = environment;
}
#Bean
SpanLogger getLogger() {
return new ProfileLogger("", environment);
}
}
This prints logs like the following:
2017-03-13 14:47:02.796 INFO 22481 --- [ main] com.company.app.Application : Started Application in 16.115 seconds (JVM running for 16.792)
2017-03-13 14:47:32.684 [localhost, swagger] TRACE 22481 --- [pool-2-thread-1] c.c.app.config.ProfileLogger : Starting span: [Trace: bfcdd2ce866efbff, Span: bfcdd2ce866efbff, Parent: null, exportable:true]
This is already good but not completely what I'm looking for yet.
I'd like to add the profile from the beginning -> even the "Started Application" should contain the profile - if possible. Secondly, I'd like to move the profiles between INFO and 22481.
One more question came up during implementation: In the linked implementation there is this statement:
if (this.log.isTraceEnabled()) {
this.log.trace(text, span);
}
does that mean you only send traces if log-level is set to TRACE? If so, how could I improve logging to stdout with that approach (given a log-level of debug/info/warn)? I think the log-pattern is overriden by Sleuth/Zipkin upon importing the dependencies and thus, local logging looks the same as tracing. Eventually I'm interested in having the profile displayed in local stdout as well as in Zipkin.
EDIT 2: With the help of Marcin, I have changed the pattern by introducing a resources/logback-spring.xml file containing these lines:
<springProperty scope="context" name="activeSpringProfiles" source="spring.profiles.active"/>
<!-- Example for logging into the build folder of your project -->
<property name="LOG_FILE" value="${BUILD_FOLDER:-build}/${activeSpringProfiles:-}"/>​
<!-- You can override this to have a custom pattern -->
<property name="CONSOLE_LOG_PATTERN"
value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) [${activeSpringProfiles:-}] %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/>
Note that you have to add a bootstrap.yml file too in order to have the application name correctly displayed. Without a bootstrap.yml file, the above log-pattern just prints "bootstrap" as application name.
The bootstrap.yml just contains
spring:
application:
name: app
in my case. Everything else is configured in application-[profile].yml
Now everything works as desired:
2017-03-13 15:58:21.291 INFO [app,,,] [localhost,swagger] 27519 --- [ main] com.company.app.keyserver.Application : Started Application in 17.565 seconds (JVM running for 18.232)
Of course - you have to just provide your own logging format (e.g. via logging.pattern.level - check https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-logging.html for more info). Then you have to register your own SpanLogger bean implementation where you will take care of adding the value of a spring profile via MDC (you can take a look at this as an example https://github.com/spring-cloud/spring-cloud-sleuth/blob/master/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/Slf4jSpanLogger.java )
UPDATE:
There's another solution for more complex approach that seems much easier than rewriting Sleuth classes. You can try the logback-spring.xml way like here - https://github.com/spring-cloud-samples/sleuth-documentation-apps/blob/master/service1/src/main/resources/logback-spring.xml#L5-L11 . I'm resolving the application name there so maybe you could do the same with active profiles and won't need to write any code?

How can I use a service from a Java custom log appender in Grails?

The basic problem is that I want to use a service from a Java custom log appender in Grails. I have defined a bean in my resources.groovy:
beans = {
databaseLogAppender(DatabaseLogAppender) {
logMessageService = ref("logMessageService")
}
}
...and I have added my log appender in my Config.groovy:
appenders {
//...
appender new DatabaseLogAppender(name: 'databaseLog', threshold: org.apache.log4j.Level.WARN)
}
root {
warn 'databaseLog'
// ...
}
I can see that my log appender gets called when there is a log of level WARN but my service does not get injected so I get a NPE instead. It seems to me that I should configure the appender in some other way so that I use the bean that I have defined instead of the class directly, but I cannot figure out how to do this. Any ideas?
I figured it out.
I added my appender programatically in the BootStrap.groovy instead of configuring it in my Config.groovy. This way the beans have been properly initialized.
My BootStrap.groovy:
class BootStrap {
def databaseLogAppender
def init = { servletContext ->
Logger.getRootLogger().addAppender(databaseLogAppender)

Spring environment properties from file

I'm trying to figure out how to get the values of a properties file into my Spring Environment properties.
The pre Spring 3.1 way of doing this would be something like:
<context:property-placeholder location="classpath:my.properties" />
<bean id="myBean" class="com.whatever.MyBean">
<property name="someValue" value="${myProps.value}" />
<!-- etc -->
</bean>
I could have also done this:
public class MyBean {
#Value(value = "#{myProps.value}")
private String someValue;
}
Now that I can ostensibly pull properties from the Environment class, this seems like a much cleaner way of getting properties than using the clunky #{myProps.value} syntax in either my xml or my bean itself.
I tried this in my XML:
<bean
class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<property name="location">
<value>classpath:my.properties</value>
</property>
</bean>
But the properties are not added to Environment.
I understand that I can use the PropertySource attribute, but I'm not doing my configuration with annotations.
So how can I set up my bean / xml so that variables setup in my props are available in Environment? Moreover, how can I inject those values into my bean without having to explicitly say "environmentInstance.getProperty("myProps.value")?
For the web applications
If you want to have Environment populated before the XML bean definitions are processed, you should implement a ApplicationContextInitializer(), as described in the Spring Source blog
<context-param>
<param-name>contextInitializerClasses</param-name>
<param-value>com.bank.MyInitializer</param-value>
</context-param>
Here is a slightly modified version of the example from the blog
public class MyInitializer implements
ApplicationContextInitializer<ConfigurableWebApplicationContext> {
public void initialize(ConfigurableWebApplicationContext ctx) {
ResourcePropertySource ps;
try {
ps = new ResourcePropertySource(new ClassPathResource(
"my.properties"));
} catch (IOException e) {
throw new AssertionError("Resources for my.properties not found.");
}
ctx.getEnvironment().getPropertySources().addFirst(ps);
}
}
For the standalone applications
Basically the same thing, you can modify the environment of the AbstractApplicationContext directly
ResourcePropertySource ps;
try {
ps = new ResourcePropertySource(new ClassPathResource(
"my.properties"));
}catch (IOException e) {
throw new AssertionError("Resources for my.properties not found.");
}
//assuming that ctx is an AbstractApplicationContext
ctx.getEnvironment().getPropertySources().addFirst(ps);
P.S. the earlier version of this answer showed an attempt to modify the Environment from the bean but it seems to be an antipattern spreading on SO, because for sure you would like to have Environment property sources list populated even before the XmlBeanDefinitionReader starts to process XML to make placeholders work inside the <import/> statement.
My understanding is also a little addled but this is what I could make out:
Environment is a way to indicate the currently active profiles (and profiles are a way to selectively create beans)
PropertySources can be associated to an environment and the way to do that is using #PropertySource annotation, there does not seem to be an xml equivalent to do this.
Your understanding actually should be the other way round, AFAIK: when you declare <context:property-placeholder the placeholder will resolve properties against the locally declared properties and can fallback on the property sources declared in the environment, environment itself does not get modified with the new properties. Again,the only way to add properties to the environment itself seems to be through the #PropertySource annotation

Resources