Spring Boot and Logback by Example - spring

I have a Spring Boot app that (basically) has the following project structure:
myapp/
src/
<All Java source code here>
build.gradle
application.yml
logback.groovy
And its build.gradle dependencies are:
dependencies {
compile(
'org.springframework.boot:spring-boot-starter-actuator'
,'org.springframework.boot:spring-boot-starter-jetty'
,'org.springframework.boot:spring-boot-starter-security'
,'org.springframework.boot:spring-boot-starter-thymeleaf'
,'org.apache.commons:commons-lang3:3.4'
,'ch.qos.logback:logback-parent:1.1.7'
)
compile('org.springframework.boot:spring-boot-starter-web') {
exclude module: 'spring-boot-starter-tomcat'
}
}
The application.yml:
logging:
level:
org.springframework.web: 'DEBUG'
server:
error:
whitelabel:
enabled: false
spring:
datasource:
test-on-borrow: true
validation-query: SELECT 1
messages:
basename: i18n/messages
And the logback.groovy:
statusListener(OnConsoleStatusListener)
appender('CONSOLE', ConsoleAppender) {
encoder(PatternLayoutEncoder) {
pattern = '%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n'
}
}
appender('ROLLING', RollingFileAppender) {
encoder(PatternLayoutEncoder) {
Pattern = '%d %level %thread %mdc %logger - %m%n'
}
rollingPolicy(TimeBasedRollingPolicy) {
FileNamePattern = '/Users/myuser/logs/myapp/myapp-%d{yyyy-MM}.zip'
}
}
When I run ./gradlew bootRun -Pspring.config=. (where spring.config=. implies the myapp/application.yml config file), I see console output only. Nothing gets logged to /Users/myuser/logs/myapp/. Any ideas as to why?
Update
I have created a barebones Spring Boot app that uses my identical Logback config:
https://github.com/hotmeatballsoup/spring-boot-logback-example
Clone it and run it by running:
./gradlew bootRun -Pspring.config=.
Note that even though the app starts up fine (and you will see lots of console output), that it does not in fact create a /var/log/spring-boot-logback-example/spring-boot-logback-example.log file as expected!

add to following to logback.groovy
root(DEBUG, ["CONSOLE", "ROLLING"])

I am able to run the application successfully after making the following changes to your code:
Moved line root(DEBUG, ["CONSOLE", "ROLLING"]) in logback.groovy to the last line, after the appenders are declared
I changed the path of the log file from /var/log to home directory and the logs started appearing in console and log file. The reason for logs not being generated in /var/log directory is lack of permissions

Create a src/main/resources folder and place logback.groovy in it.
That worked for me with your project (cloned from Github) and the following logback file:
import ch.qos.logback.classic.encoder.PatternLayoutEncoder
import ch.qos.logback.core.rolling.RollingFileAppender
import ch.qos.logback.core.rolling.TimeBasedRollingPolicy
import static ch.qos.logback.classic.Level.DEBUG
appender("FILE", RollingFileAppender) {
file = "logFile.log"
rollingPolicy(TimeBasedRollingPolicy) {
fileNamePattern = "logFile.%d{yyyy-MM-dd}.log"
maxHistory = 30
}
encoder(PatternLayoutEncoder) {
pattern = "%-4relative [%thread] %-5level %logger{35} - %msg%n"
}
}
root(DEBUG, ["FILE"])

With logback/spring boot and a groovy config. The custom log configuration should be in a file named logback-spring.groovy instead of logback.groovy
The file should exist in src/main/resources.
see reference spring boot logging docs

Related

How do I disable spring security in a Grails 4 integration test?

I had a grails 3 app including spring security, which I recently upgraded to grails 4.
My application.yml includes the following:
environments:
test:
grails:
plugin:
springsecurity:
active: false
security:
ignored: '/**'
basic:
enabled: false
spring:
security:
enabled: false
Why doesn't this work in Grails 4? What's a good alternative solution?
Grails 4 seems to be ignoring this configuration. When I run integration tests, I am getting a 403 error with a message:
Could not verify the provided CSRF token because your session was not found.
It seems like spring security enabled, and it's using SecurityFilterAutoConfiguration, which is normally excluded for my app.
Update
I am using the following dependencies:
compile('org.grails.plugins:spring-security-core:3.2.3') {
exclude group: 'org.springframework.security'
}
compile ('org.springframework.security:spring-security-core:4.2.13.RELEASE') {
force = true
}
compile 'org.springframework.security:spring-security-web:4.2.13.RELEASE'
compile 'org.springframework.security:spring-security-config:4.2.13.RELEASE'
Update 2:
In my debugger, I found that the spring security core plugin actually is being disabled. The following code from the plugin class is executed:
SpringSecurityUtils.resetSecurityConfig()
def conf = SpringSecurityUtils.securityConfig
boolean printStatusMessages = (conf.printStatusMessages instanceof Boolean) ? conf.printStatusMessages : true
if (!conf || !conf.active) {
if (printStatusMessages) {
// <-- the code in this block is executed; active flag is false
String message = '\n\nSpring Security is disabled, not loading\n\n'
log.info message
println message
}
return
}
...however, I am still getting the CSRF filter error, so Spring Security must be configuring itself somehow regardless.
Update 3:
The CSRF filter is being set up by ManagementWebSecurityConfigurerAdapter, using the default configuration.
I tried adding the following to resources.groovy:
if (grailsApplication.config.disableSecurity == true && !Environment.isWarDeployed()) {
webSecurityConfigurerAdapter(new WebSecurityConfigurerAdapter(true) {})
}
This did not fix the issue. Although my anonymous WSCA bean is being constructed, the MWSCA default bean is still being used by spring.
Try this in
grails-app/conf/application.groovy
environments {
development {
}
test {
grails.plugin.springsecurity.active = false
}
production {
}
}

How to set the log level in Grails 4

In my Grails 4 app, log.info("log message") doesn't show log, but log.error("log message") does.
How do I change the log level from error to info in Grails 4?
Option 1
All I needed to do was update the application.yml file and added the following to the bottom
logging:
level:
root: INFO
You can also set a single the log level for a single package:
logging:
level:
packageName: INFO
Option 2
Since Grails 4 is based on Spring Boot, I ended up just setting the appropriate environment variable, i.e. logging.level.root=INFO or logging.level.com.mycompany.mypackage=INFO which I did in intellij by editing my run configuration (see below). This way, I can set the logging level differently when I deploy it.
You want to edit grails-app/conf/logback.groovy. Below is what the default file looks like for Grails 4.0.1.
import grails.util.BuildSettings
import grails.util.Environment
import org.springframework.boot.logging.logback.ColorConverter
import org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter
import java.nio.charset.StandardCharsets
conversionRule 'clr', ColorConverter
conversionRule 'wex', WhitespaceThrowableProxyConverter
// See http://logback.qos.ch/manual/groovy.html for details on configuration
appender('STDOUT', ConsoleAppender) {
encoder(PatternLayoutEncoder) {
charset = StandardCharsets.UTF_8
pattern =
'%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} ' + // Date
'%clr(%5p) ' + // Log level
'%clr(---){faint} %clr([%15.15t]){faint} ' + // Thread
'%clr(%-40.40logger{39}){cyan} %clr(:){faint} ' + // Logger
'%m%n%wex' // Message
}
}
def targetDir = BuildSettings.TARGET_DIR
if (Environment.isDevelopmentMode() && targetDir != null) {
appender("FULL_STACKTRACE", FileAppender) {
file = "${targetDir}/stacktrace.log"
append = true
encoder(PatternLayoutEncoder) {
charset = StandardCharsets.UTF_8
pattern = "%level %logger - %msg%n"
}
}
logger("StackTrace", ERROR, ['FULL_STACKTRACE'], false)
}
root(ERROR, ['STDOUT'])
The specific change depends on what you really want to do. For example, if you have a controller named demo.SomeController and you want to set its log level to INFO, you could add something like this:
logger 'demo.SomeController', INFO, ['STDOUT'], false
See http://logback.qos.ch/manual/groovy.html for the full config reference.
I hope that helps.
Simple Way:
Update/Replace your grails-app/conf/logback.groovy with following code:
import ch.qos.logback.classic.encoder.PatternLayoutEncoder
appender("FILE", RollingFileAppender) {
file = "logs/FILE-NAME.log"
rollingPolicy(TimeBasedRollingPolicy) {
fileNamePattern = "logs/FILE-NAME-%d{yyyy-MM-dd}.log"
maxHistory = 30
}
encoder(PatternLayoutEncoder) {
pattern = "%d{HH:mm:ss.SSS} %-4relative [%thread] %-5level %logger{35} - %msg%n"
}
}
root(INFO, ["FILE"])
Above solution shows logger level to INFO
You can refer this more details and all log levels.
Hope this will helps you.

how do i configure static versioning (digest) for the asset-pipeline (bertramlabs) in my spring boot 2 application?

spring boot version: 2.0.4.RELEASE
asset-pipeline version: 3.0.3
Hi
we're using this plugin, because we know it from our grails applications.
We liked it, because it has a simple configuration (for our requirements)
Now we're developing a spring boot application and we used this plugin too and we're (almost) happy with it.
But when we run the application in the development mode the assets don't have a digest like /assets/my-styles-b5d2d7380a49af2d7ca7943a9aa74f62s.css
How do i configure the plugin to create a digest for all our resources?
currently we're using this configuration:
assets {
minifyJs = true
minifyCss = true
enableSourceMaps = false
includes = ["application.js", "application.scss"]
}
And we're using thymeleaf for our templates:
<link th:href="#{/assets/application.css}" rel="stylesheet">
I found a solution...
when you use the asset-pipeline, you get a gradle task assetCompile.
when creating a .war file, you can add this gradle task and replace all the assets with the versioned files.
when you want to use the versioned files in your production mode you have to use this configuration (build.gradle)
assets {
minifyJs = true
minifyCss = true
skipNonDigests = true
packagePlugin = true
includes = ["application.js", "application.scss"]
}
...
war {
dependsOn 'assetCompile'
from( "${buildDir}/assets", {
into "/WEB-INF/classes/META-INF/assets"
})
baseName = '<your project>'
enabled = true
}
that's all.
When running the assetCompile task, a manifest.properties file is created. This file contains the mapping of the original filename and the versioned one.
This file is used by the application to find the correct resource, e.g. application.css=application-79a3c8a2f085ecefadgfca3cda6fe3d12.css
I created a plugin which enables the url replacement for assets with digest in the production mode:
Dependency
compile 'ch.itds.taglib:asset-pipeline-thymeleaf-taglib:1.0.0'
Configuration
#Configuration
public class ThymeleafConfig {
#Bean
public AssetDialect assetDialect() {
return new AssetDialect();
}
}
Usage
<html xmlns:asset="https://www.itds.ch/taglib/asset">
<script asset:src="#{/assets/main.js}"></script>
</html>
The asset:src="#{/assets/main.js}" will be replaced with src="/assets/main-DIGEST.js".
The replacement happens only if the developmentRuntime of the asset pipeline is disabled.
A little bit more details are available on my blog post: https://kobelnet.ch/Blog/2019/03/12/assetpipelinethymeleaftaglib

SonarQube - specify location of sonar.properties

I'm trying to deploy SonarQube on Kubernetes using configMaps.
The latest 7.1 image I use has a config in sonar.properties embedded in $SONARQUBE_HOME/conf/ . The directory is not empty and contain also a wrapper.conf file.
I would like to mount the configMap inside my container in a other location than /opt/sonar/conf/ and specify to sonarQube the new path to read the properties.
Is there a way to do that ? (environment variable ? JVM argument ? ...)
It is not recommended to modify this standard configuration in any way. But we can have a look at the SonarQube sourcecode. In this file you can find this code for reading the configuration file:
private static Properties loadPropertiesFile(File homeDir) {
Properties p = new Properties();
File propsFile = new File(homeDir, "conf/sonar.properties");
if (propsFile.exists()) {
...
} else {
LoggerFactory.getLogger(AppSettingsLoaderImpl.class).warn("Configuration file not found: {}", propsFile);
}
return p;
}
So the conf-path and filename is hard coded and you get a warning if the file does not exist. The home directory is found this way:
private static File detectHomeDir() {
try {
File appJar = new File(Class.forName("org.sonar.application.App").getProtectionDomain().getCodeSource().getLocation().toURI());
return appJar.getParentFile().getParentFile();
} catch (...) {
...
}
So this can also not be changed. The code above is used here:
#Override
public AppSettings load() {
Properties p = loadPropertiesFile(homeDir);
p.putAll(CommandLineParser.parseArguments(cliArguments));
p.setProperty(PATH_HOME.getKey(), homeDir.getAbsolutePath());
p = ConfigurationUtils.interpolateVariables(p, System.getenv());
....
}
This suggests that you can use commandline parameters or environment variables in order to change your settings.
For my problem, I defined environment variable to configure database settings in my Kubernetes deployment :
env:
- name: SONARQUBE_JDBC_URL
value: jdbc:sqlserver://mydb:1433;databaseName=sonarqube
- name: SONARQUBE_JDBC_USERNAME
value: sonarqube
- name: SONARQUBE_JDBC_PASSWORD
valueFrom:
secretKeyRef:
name: sonarsecret
key: dbpassword
I needed to use also ldap plugin but it was not possible to configure environment variable in this case. As /opt/sonarqube/conf/ is not empty, I can't use configMap to decouple configuration from image content. So, I build my own sonarqube image adding the ldap jar plugin and ldap setting in sonar.properties :
# General Configuration
sonar.security.realm=LDAP
ldap.url=ldap://myldap:389
ldap.bindDn=CN=mysa=_ServicesAccounts,OU=Users,OU=SVC,DC=net
ldap.bindPassword=****
# User Configuration
ldap.user.baseDn=OU=Users,OU=SVC,DC=net
ldap.user.request=(&(sAMAccountName={0})(objectclass=user))
ldap.user.realNameAttribute=cn
ldap.user.emailAttribute=mail
# Group Configuration
ldap.group.baseDn=OU=Users,OU=SVC,DC=net
ldap.group.request=(&(objectClass=group)(member={dn}))

Expanding application.yml during Gradle processResources gives MissingPropertyException

To replaces properties in my Spring Boot application.yml I've added:
processResources {
filesMatching("**/application.yml") {
expand(project.properties)
}
}
The replacement fails but gives a MissingPropertyException:
Caused by: groovy.lang.MissingPropertyException: No such property: OPENSHIFT_MYSQL_DB_HOST for class: SimpleTemplateScript1
at SimpleTemplateScript1.run(SimpleTemplateScript1.groovy:49)
at org.gradle.api.internal.file.copy.FilterChain$3.transform(FilterChain.java:95)
at org.gradle.api.internal.file.copy.FilterChain$3.transform(FilterChain.java:84)
at org.gradle.api.internal.ChainingTransformer.transform(ChainingTransformer.java:37)
at org.gradle.api.internal.file.copy.FilterChain.transform(FilterChain.java:39)
at org.gradle.api.internal.file.copy.FilterChain.transform(FilterChain.java:46)
at org.gradle.api.internal.file.copy.DefaultFileCopyDetails.open(DefaultFileCopyDetails.java:86)
at org.gradle.api.internal.file.AbstractFileTreeElement.copyTo(AbstractFileTreeElement.java:56)
at org.gradle.api.internal.file.copy.DefaultFileCopyDetails.copyTo(DefaultFileCopyDetails.java:94)
at org.gradle.api.internal.file.AbstractFileTreeElement.copyFile(AbstractFileTreeElement.java:93)
at org.gradle.api.internal.file.AbstractFileTreeElement.copyTo(AbstractFileTreeElement.java:74)
... 81 more
Originally my application.yml contained:
url: jdbc:mysql://${OPENSHIFT_MYSQL_DB_HOST}:${OPENSHIFT_MYSQL_DB_PORT}/${OPENSHIFT_APP_NAME}
Note these Openshift variables are only know on Openshift production environment but not when running locally in dev mode.
As stated on http://www.gradle.org/docs/current/dsl/org.gradle.api.tasks.Copy.html: You can also include arbitrary Groovy code in the file, such as ${version ?: 'unknown'} so I changed my application.yml to:
url: jdbc:mysql://${OPENSHIFT_MYSQL_DB_HOST ?: ''}:${OPENSHIFT_MYSQL_DB_PORT ?: ''}/${OPENSHIFT_APP_NAME ?: ''}
But this gives the same MissingPropertyException.
Am I missing something here?
The Gradle expand ${..} style conflicts with the same Spring property placeholder style and therefor needs to be escaped like \${..}.
This is added to Spring Boot docs now: https://github.com/spring-projects/spring-boot/commit/c0c67f2593dbfd17aa304b43f4da3a3678fa58eb

Resources