Different Log4j file paths for each webapp - spring

I'm trying to use different logs for each webapp. I've multiples applications in the same server and I need to load this directory from a properties file.
Each web app loads a property file with this keys/values (X it's the app's name, different for all):
app=APP.X
logPath=C://logs
I have added a log4j.properties file in each webapp project with all parameters that I need except the log's path (log4j.properties)
# Root logger option
log4j.rootLogger=INFO, stdout, file
# Redirect log messages to console
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c:%M:%L - %m%n
# Redirect log messages to a log file, support file rolling.
log4j.appender.file=org.apache.log4j.DailyRollingFileAppender
log4j.appender.file.encoding=UTF-8
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c:%M:%L - %m%n
#log4j.appender.file.MaxBackupIndex=8
log4j.appender.file.DatePattern='.'yyyy-MM
I have created a Java class in my util jar that implements ApplicationListener and adds the key log4j.appender.file.File (Log4jContextListener.java)
public class Log4jContextListener implements ApplicationListener<ContextStartedEvent>
{
#Value("${rutaLogs}")
private String rutaLogs;
#Value("${app}")
private String app;
private Logger log = Logger.getLogger(getClass());
#Override
public void onApplicationEvent(ContextStartedEvent arg0)
{
Properties props = new Properties();
InputStream strm = null;
try
{
System.out.println("LOADING APP: " + app);
strm = Log4jContextListener.class.getClassLoader().getResourceAsStream("log4j.properties");
props.load(strm);
if (props != null)
log.info("PROP: " + props.getProperty("log4j.appender.file.layout"));
props.put("log4j.appender.file.File", logPath+ app + File.separator + app + ".log");
}
catch (IOException propsLoadIOE)
{
throw new Error("can't load logging config file", propsLoadIOE);
}
finally
{
try
{
strm.close();
}
catch (IOException configCloseIOE)
{
throw new Error("error closing logging config file", configCloseIOE);
}
}
// props.put("webAppRoot", event.getServletContext().getRealPath("/"));
PropertyConfigurator.configure(props);
}
}
And each web app adds a bean with this declaration in the applicationContext.xml
<!-- Listener to initialize LOG4J -->
<bean id="log4jConfigurationListener" class="com.framework.listeners.Log4jContextListener" />
The log's folders and files are creating right but the log traces are mixed, each webapp use only the latest location loaded.
How can I do diferent in each wepapp context?
PD: I have to do it reading from properties, I can't use environment vars.

Hi I manage to set log location per webapp context with the following:
package ...
import org.apache.log4j.Logger;
import org.apache.log4j.lf5.LogLevel;
import org.springframework.web.WebApplicationInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import java.io.InputStream;
import java.util.Properties;
import java.util.TimeZone;
public class SystemPropertyDefaultsInitializer implements WebApplicationInitializer {
private static final Logger LOG = Logger.getLogger(SystemPropertyDefaultsInitializer.class);
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
String warName = new File(servletContext.getRealPath("/")).getName();
InputStream resourceAsStream = getClass().getClassLoader().getResourceAsStream("log4j.properties");
Properties properties = PropertiesHelper.loadApplicationProperties(resourceAsStream);
String logLocation = "/var/log/tomcat7/" + warName + ".log";
File file = new File(logLocation);
if (!file.exists()) {
FileOutputStream fos = new FileOutputStream(file);
fos.close();
}
if (!file.canWrite()) {
throw new IOException("Write permission denied for file \"" + logLocation + "\"");
}
LOG.info("Log location set to: \"" + logLocation + "\"");
properties.setProperty("log4j.appender.FILE.File", logLocation);
PropertyConfigurator.configure(properties);
}
}

Related

How to setup a log4j2 custom appender with Spring Boot?

I am facing challenges on creating a custom appender for my spring boot application. The following exception is thrown:
2019-11-08 10:33:27,897 main ERROR Unable to locate plugin type for
mycustomhandlerclass
2019-11-08 10:33:27,961 main ERROR Unable to locate plugin for
mycustomhandlerclass
2019-11-08 10:33:27,961 main ERROR Unable to invoke factory method in
class org.apache.logging.log4j.core.config.AppendersPlugin for element
Appenders: java.lang.NullPointerException java.lang.NullPointerException
Spring boot version : 2.1.7
Maven plugin version: 3.1.1
Mycustomhandlerclass.java:
import java.io.Serializable;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginElement;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
#Plugin(category = "Core", name = "Mycustomhandlerclass", elementType = "appender")
public class Mycustomhandlerclass extends AbstractAppender {
private final Logger logger = LogManager.getLogger(getClass().getName());
#SuppressWarnings("deprecation")
protected AmtAppender(String name, Filter filter, Layout<? extends Serializable> layout, boolean ignoreExceptions) {
super(name, filter, layout,ignoreExceptions);
// TODO Auto-generated constructor stub
}
#PluginFactory
public static Mycustomhandlerclass createAppender(#PluginAttribute("name") String name, #PluginAttribute("ignoreExceptions") boolean ignore, #PluginElement("Filter") Filter filter, #PluginElement("Layout") Layout<? extends Serializable> layout) {
return new Mycustomhandlerclass(name, filter, layout, ignore);
}
#Override
public void append(LogEvent event) {
// TODO Auto-generated method stub
logger.info("Entered CustomAppender");
}
}
log4j2.properties:
name=PropertiesConfig
appenders = file, errorFile, customAppender
property.filename = D:/logdestinationfolder
appender.file.type = RollingFile
appender.file.name = OUTFILE
appender.file.fileName = ${filename}/console.log
appender.file.filePattern = ${filename}/console%d{yyyy-MM-dd}.log
appender.file.layout.type = PatternLayout
appender.file.layout.pattern = %-5p %d{dd-MM-yyyy HH:mm:ss} %t %c{1} %m%n
appender.file.policies.type = Policies
appender.file.policies.time.type = TimeBasedTriggeringPolicy
appender.file.policies.time.interval = 1
appender.file.strategy.type=DefaultRolloverStrategy
appender.file.strategy.max=5
appender.errorFile.type = RollingFile
appender.errorFile.name = ERROROUTFILE
appender.errorFile.fileName = ${filename}/error.log
appender.errorFile.filePattern = ${filename}/error%d{yyyy-MM-dd}.log
appender.errorFile.layout.type = PatternLayout
appender.errorFile.layout.pattern = %-5p %d{dd-MM-yyyy HH:mm:ss} %t %c{1} %M() :: %L %m%n
appender.errorFile.policies.type = Policies
appender.errorFile.policies.time.type = TimeBasedTriggeringPolicy
appender.errorFile.policies.time.interval = 1
appender.errorFile.strategy.type=DefaultRolloverStrategy
appender.errorFile.strategy.max=5
appender.errorFile.filter.1.type=ThresholdFilter
appender.errorFile.filter.1.onMatch=ACCEPT
appender.errorFile.filter.1.level=ERROR
appender.customAppender.type = Mycustomhandlerclass
appender.customAppender.name = CUSTOMAPPENDER
appender.customAppender.filter.1.type=ThresholdFilter
appender.customAppender.filter.1.onMatch=ACCEPT
appender.customAppender.filter.1.level=WARN
rootLogger.level = ERROR
loggers=appLogger
logger.appLogger.name = com.solartis
logger.appLogger.level = DEBUG
logger.appLogger.appenderRefs=errorFile,file,customAppender
logger.appLogger.appenderRef.errorFile.ref = ERROROUTFILE
logger.appLogger.appenderRef.file.ref = OUTFILE
logger.appLogger.appenderRef.customAppender.ref = CUSTOMAPPENDER
Note: On removing the appender references in log4j2.properties, the logs are written to files in the mentioned path in log4j2.properties. So, the problem is with customappender integration.
You have to add the package of your appender class in log4j.properties e.g.
packages = org.home.appenders
The error is in this code:-
protected AmtAppender(String name, Filter filter, Layout<? extends Serializable> layout, boolean ignoreExceptions) {
super(name, filter, layout,ignoreExceptions);
// TODO Auto-generated constructor stub
}
What is AmtAppender? The constructor name should be same as the class name. You need to provide the constructor in this manner:-
#SuppressWarnings("deprecation")
protected MyCustomAppender(String name, Filter filter)
{
super(name, filter, null);
}

spring boot list all application properties of an app [duplicate]

There is already a question asking for logging the active configuration, there is a correct answer but the problem is that the configuration is logged only if all beans are correctly instantiated. I would like to log all properties even (mainly) if the application crash at startup. My question is more specific:
How to log all active properties of a spring boot application before the beans instantiation?
For doing this you need to register an ApplicationListener. The event to catch is the ApplicationPreparedEvent, according to the documentation:
ApplicationPreparedEvent is an event published when a SpringApplication is starting up and the
ApplicationContext is fully prepared but not refreshed. The bean
definitions will be loaded and the Environment is ready for use at
this stage.
The main method would look like this:
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(MyApplication.class);
springApplication.addListeners(new PropertiesLogger());
springApplication.run(args);
}
I've reused the code of the answer cited in the current question but I've modified it because the context you get is not already refreshed and the structure of the environment is not exactly the same as after the startup of the application. I've also printed the properties by property sources: one for the the system environment, one for the system properties, one for the application configuration properties, etc... Note also that the ApplicationPreparedEvent can be triggered multiple times, and that properties are printed only the first time. See Spring Boot issue #8899 for details.
package com.toto.myapp.util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.event.ApplicationPreparedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.PropertySource;
import java.util.LinkedList;
import java.util.List;
public class PropertiesLogger implements ApplicationListener<ApplicationPreparedEvent> {
private static final Logger log = LoggerFactory.getLogger(PropertiesLogger.class);
private ConfigurableEnvironment environment;
private boolean isFirstRun = true;
#Override
public void onApplicationEvent(ApplicationPreparedEvent event) {
if (isFirstRun) {
environment = event.getApplicationContext().getEnvironment();
printProperties();
}
isFirstRun = false;
}
public void printProperties() {
for (EnumerablePropertySource propertySource : findPropertiesPropertySources()) {
log.info("******* " + propertySource.getName() + " *******");
String[] propertyNames = propertySource.getPropertyNames();
Arrays.sort(propertyNames);
for (String propertyName : propertyNames) {
String resolvedProperty = environment.getProperty(propertyName);
String sourceProperty = propertySource.getProperty(propertyName).toString();
if(resolvedProperty.equals(sourceProperty)) {
log.info("{}={}", propertyName, resolvedProperty);
}else {
log.info("{}={} OVERRIDDEN to {}", propertyName, sourceProperty, resolvedProperty);
}
}
}
}
private List<EnumerablePropertySource> findPropertiesPropertySources() {
List<EnumerablePropertySource> propertiesPropertySources = new LinkedList<>();
for (PropertySource<?> propertySource : environment.getPropertySources()) {
if (propertySource instanceof EnumerablePropertySource) {
propertiesPropertySources.add((EnumerablePropertySource) propertySource);
}
}
return propertiesPropertySources;
}
}
📝 Show the Properties BEFORE application is ready
In my case, I needed to show the properties before the context is loaded. While debugging the app, I would like to log all the properties so that I know what's going on...
☕ Kotlin Implementation
As described at https://www.baeldung.com/spring-boot-environmentpostprocessor, the properties can be collected before the context is loaded through the use of EnvironmentPostProcessor, which is instantiated as part of Spring Factories from the call ConfigFileApplicationListener.loadPostProcessors(). At this point, you can collect all the properties and show in any specific way.
NOTE: While loading properties during this event, the context isn't ready. So, are the loggers. For this reason, the properties can be loaded before the App Banner (if any)
Also, the entry for the spring factory must be present, so create it first
org.springframework.boot.env.EnvironmentPostProcessor=\
cash.app.PropertiesLoggerEnvironmentPostProcessor
Then, create the logger
package cash.app
import org.springframework.boot.SpringApplication
import org.springframework.boot.env.EnvironmentPostProcessor
import org.springframework.core.Ordered
import org.springframework.core.annotation.Order
import org.springframework.core.env.ConfigurableEnvironment
import org.springframework.core.env.EnumerablePropertySource
import java.util.*
/**
* This is to log the properties (config and system) before the app loads. This way, we know what will be loaded
* on the app.
* Note that we can't use the logger because the context hasn't built yet at the time it loads the properties twice.
*
* As an event consumer, the method ConfigFileApplicationListener.onApplicationEnvironmentPreparedEvent is called
* while the context is building. The process is described at https://www.baeldung.com/spring-boot-environmentpostprocessor
* and one important aspect is that this class is an EnvironmentPostProcessor, only loaded before the App is loaded
* with the assistance of the "src/main/resources/META-INF/spring.factories". It is loaded by the
* ConfigFileApplicationListener.loadPostProcessors(), which looks for the list of classses in the factories.
*
* https://www.baeldung.com/spring-boot-environmentpostprocessor explains how to create AutoConfiguration classes for
* shared libraries. For the case of config, the reload of properties is detailed and explained on the docs at
* https://www.baeldung.com/spring-reloading-properties
*
* TODO: We need to hide the secrets, if they are defined here.
*
* #author Marcello.DeSales#gmail.com
*/
#Order(Ordered.LOWEST_PRECEDENCE)
class PropertiesLoggerEnvironmentPostProcessor : EnvironmentPostProcessor {
companion object {
/**
* Sharing is started immediately and never stops.
*/
private var numberOfPasses: Int = 0
private var systemProperties: MutableMap<String, String> = mutableMapOf()
}
override fun postProcessEnvironment(environment: ConfigurableEnvironment, application: SpringApplication) {
for (propertySource in findPropertiesPropertySources(environment)) {
// Avoid printing the systemProperties twice
if (propertySource.name.equals("systemProperties")) {
numberOfPasses = numberOfPasses?.inc()
} else {
System.out.println("******* \" + ${propertySource.getName()} + \" *******" )
}
// Adaptation of https://stackoverflow.com/questions/48212761/how-to-log-all-active-properties-of-a-spring-boot-application-before-the-beans-i/48212783#48212783
val propertyNames = propertySource.propertyNames
Arrays.sort(propertyNames)
for (propertyName in propertyNames) {
val resolvedProperty = environment!!.getProperty(propertyName!!)
val sourceProperty = propertySource.getProperty(propertyName).toString()
if (resolvedProperty == sourceProperty) {
if (propertySource.name.equals("systemProperties")) {
systemProperties.put(propertyName, resolvedProperty)
} else {
System.out.println( "${propertyName}=${resolvedProperty}" )
}
} else {
if (propertySource.name.equals("systemProperties")) {
systemProperties.put(propertyName, resolvedProperty ?: "")
} else {
System.out.println( "${propertyName}=${sourceProperty} ----- OVERRIDDEN =>>>>>> ${propertyName}=${resolvedProperty}" )
}
}
}
}
// The system properties show up twice in the process... The class is called twice and we only print it in the end.
if (numberOfPasses == 2) {
System.out.println("******* \" System Properties \" *******")
val sysPropertyNames = systemProperties.keys.sorted()
for (sysPropertyName in sysPropertyNames) {
val sysPropertyValue = systemProperties!!.get(sysPropertyName!!)
System.out.println( "${sysPropertyName}=${sysPropertyValue}" )
}
}
}
private fun findPropertiesPropertySources(environment: ConfigurableEnvironment): List<EnumerablePropertySource<*>> {
val propertiesPropertySources: MutableList<EnumerablePropertySource<*>> = LinkedList()
for (propertySource in environment!!.propertySources) {
if (propertySource is EnumerablePropertySource<*>) {
if (propertySource.name.equals("systemProperties") || propertySource.name.contains("applicationConfig:")) {
propertiesPropertySources.add(propertySource)
}
}
}
return propertiesPropertySources.asReversed()
}
}
🔊 Example Logs
Here's the loggers during the bootstrap of one of my services
/Users/marcellodesales/.gradle/jdks/jdk-14.0.2+12/Contents/Home/bin/java -XX:TieredStopAtLevel=1 -noverify -Dspring.output.ansi.enabled=always
....
....
2022-02-22T21:24:39 INFO [app=springAppName_IS_UNDEFINED,prof=observed,db,ppd_dev][tid=,sid=,sxp=][uid=] 74720 --- [ restartedMain] o.s.b.devtools.restart.ChangeableUrls : The Class-Path manifest attribute in /Users/marcellodesales/.gradle/caches/modules-2/files-2.1/com.sun.xml.bind/jaxb-core/2.2.7s-codec-1.11.jar
2022-02-22T21:24:39 INFO [app=springAppName_IS_UNDEFINED,prof=observed,db,ppd_dev][tid=,sid=,sxp=][uid=] 74720 --- [ restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : Devtools property defaults active! Set 'spring.devtools.add-properties' to 'false' to disable
******* " + applicationConfig: [classpath:/application.yaml] + " *******
management.endpoint.health.show-details=always
management.endpoints.web.base-path=/actuator ==========>>>>>> OVERRIDDEN =========>>>>>> management.endpoints.web.base-path=/orchestrator/actuator
management.endpoints.web.exposure.include=*
management.metrics.web.server.request.autotime.enabled=true
spring.application.name=orchestrator-service
spring.boot.admin.client.enabled=false ==========>>>>>> OVERRIDDEN =========>>>>>> spring.boot.admin.client.enabled=true
spring.cloud.discovery.client.composite-indicator.enabled=false
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
******* " + applicationConfig: [classpath:/application-ppd_dev.yaml] + " *******
spring.datasource.url=jdbc:postgresql://localhost:6433/supercash?createDatabaseIfNotExist=true ==========>>>>>> OVERRIDDEN
=========>>>>>> spring.datasource.url=jdbc:postgresql://localhost:6433/supercash?createDatabaseIfNotExist\=true
spring.devtools.livereload.enabled=true
spring.mail.properties.mail.smtp.starttls.required=true
spring.mail.test-connection=true
******* " System Properties " *******
LOG_LEVEL_PATTERN=%5p [,%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%X{X-Span-Export:-}]
PID=74720
com.sun.management.jmxremote=
file.encoding=UTF-8
ftion
java.vm.specification.version=14
java.vm.vendor=AdoptOpenJDK
java.vm.version=14.0.2+12
jboss.modules.system.pkgs=com.intellij.rt
jdk.debug=release
line.separator=
os.arch=x86_64
os.name=Mac OS X
os.version=10.16
user.name=marcellodesales
user.timezone=America/Los_Angeles
2022-02-22T21:25:16 DEBUG [app=orchestrator-service,prof=observed,db,ppd_dev][tid=,sid=,sxp=][uid=] 74720 --- [ restartedMain] o.s.b.c.c.ConfigFileApplicationListener : Activated activeProfiles observed,db,ppd_dev
_____ _____ _
/ ____| / ____| | |
| (___ _ _ _ __ ___ _ __| | __ _ ___| |__
2022-02-22T20:41:08 INFO [app=orchestrator-service,prof=observed,db,ppd_dev][tid=,sid=,sxp=][uid=] 74181 --- [ restartedMain]

How to upload an image or a video to a persistant folder in class-path with Spring-Boot?

I am new in Spring-Boot...
I want to upload images or videos, and store them in a persistant folder "upload-storage" in the class-path of my project in the server. I don't want to store them in the database (20 Mo).
Spring-Boot store them in target/upload-storage.
That functions : I can show the videos on the view with the controller and Thymeleaf. I can close tomcat, close the browser, and open them again : that functions.
But the day after, upload-storage is disapeared !
I think that I don't use the good process.
But I found how to upload an image : ok. I found how to show images from a folder in class-path : ok. I found how to upload images to database. But nothing to store the uploaded images in a persistant folder.
Can you help me ? Can you tell me the good process ?
Some details :
I have an entity "video" to store name, extension, length,... of the video.
I have "VideoRepository" and "VideoService" to manage the requests with "Video".
I have a "StorageService" and "StorageServiceImpl" to manage the upload of video and images : It as to upload the video and save it in a folder called "upload-storage" : I will come back on it farther.
I have a videoForm.html first with a form to select a file and send it to "UploadController", then an other form to show the video, the datas extracted from the video, modify the name or add precisions, and send this form to a "VideoController" who save the entity.
A part of the code of "UploadController" :
`
#Controller
public class UploadController extends BaseController {
private final StorageService storageServiceImpl;
#Autowired
public UploadController(StorageService storageServiceImpl) {
this.storageServiceImpl = storageServiceImpl;
}
#PostMapping("/upload")
public String recupereUpload(#RequestParam("file") MultipartFile file,Model model){
String filename ="";
try {
final long limit = 200 * 1024 * 1024;
if (file.getSize() > limit) {
model.addAttribute("message", "Taille du fichier trop grand (>200MB)");
model.addAttribute("ok", false );
}
filename = storageServiceImpl.store(file);
model.addAttribute("filename", filename);
model.addAttribute("message", "Le téléchargement de " + filename+" est réussi !");
} catch (Exception e) {
model.addAttribute("message", "FAIL to upload " + filename + "!");
model.addAttribute("ok", false );
}
Video video = new Video();
model.addAttribute("ok", true );
model.addAttribute("video", video);
String baseName = storageServiceImpl.getBaseName(filename);
String ext = storageServiceImpl.getExtension(filename);
model.addAttribute("nom", baseName);
model.addAttribute("ext", ext);
model.addAttribute("nomorigin", filename);
model.addAttribute("size", Math.round(file.getSize()/1024));
String typExt = storageServiceImpl.getType(ext);
model.addAttribute("typExt", typExt);
return "elementVideo/videoForm";
}
`
"StorageServiceImpl" has different methods :
getExtension(String filename){...}
getType(String ext){...}
getType(String ext){...}
getBaseName(String filename){...}
The main method is store(MultipartFile file) {...} :
#Service
public class StorageServiceImpl implements StorageService {
private final Path storageLocation = Paths.get("upload-storage");
#Override
public String store(MultipartFile file) {
try {
// VĂ©rification de l'existence :
if (file.isEmpty()) {
throw new Exception("Failed to store empty file " + file.getOriginalFilename() );
}
// Vérification de la nature et traitement du fichier uploadé :
String ext = getExtension(file.getOriginalFilename());
String[] extAutorise = {"mp4", "avi","ogg","ogv","jpg","jpeg","png","gif"};
String fileNameTarget ="";
if ( ArrayUtils.contains( extAutorise, ext)) {
//DĂ©finir le fichier destination :
fileNameTarget = file.getOriginalFilename();
fileNameTarget = fileNameTarget.replaceAll(" ", "_");
File dir = storageLocation.toFile();
String serverFile = dir.getAbsolutePath() + File.separator + fileNameTarget ;
try {
try (InputStream is = file.getInputStream();
BufferedOutputStream stream = new BufferedOutputStream(new FileOutputStream(serverFile))
) {
int i;
while ((i = is.read()) != -1) {
stream.write(i);
}
stream.flush();
}
} catch (IOException e) {
System.out.println("error : " + e.getMessage());
}
}
return fileNameTarget;
} catch (Exception e) {
throw new RuntimeException("FAIL!");
}
}
`
With this code, a folder "upload-storage" is created at the root of the project.
The video is uploaded in this folder...
But in "videoForm.html", the code
<video id="video" th:src="'/upload-storage/'+${filename}" height="60"
autoplay="autoplay"></video>
shows nothing.
I have an other solution.
In StorageServiceImpl, I use the code :
private final String storageLocation = this.getClass().getResource("/static/").getPath();
at place of :
private final Path storageLocation = Paths.get("upload-storage");
then :
File dir = new File(storageLocation + File.separator + "upload-storage");
at place of :
File dir = storageLocation.toFile();
then :
File serverFile = new File(dir.getAbsolutePath() + File.separator + fileNameTarget);
at place of :
String serverFile = dir.getAbsolutePath() + File.separator + fileNameTarget ;
With this solution, upload-storage is created in target folder.
I use an other controller BaseController :
public class BaseController {
public static final String PARAM_BASE_URL = "baseURL";
public String getBaseURL(HttpServletRequest request){
return request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath();
}
}
`
UploadController extends this BaseController.
I add HttpServletRequest request in recupereUpload() :
#PostMapping("/upload")
public String recupereUpload(#RequestParam("file") MultipartFile file,
Model model, HttpServletRequest request ){
I add in the model sent by recupereUpload :
model.addAttribute(PARAM_BASE_URL, getBaseURL(request));
And at last, I can see my video in videoForm.html with the code :
<video id="video" th:src="${baseURL}+'/upload-storage/'+${filename}" height="60" autoplay="autoplay"></video>
I can close Tomcat, close Eclipse, close the machine, and open all again : all is preserved and I can see the video.
But some time later : all is disappeared.
There must be a better solution.
Can you help me ?
Why dont you use Spring Content for the video content portion of your solution? That way you won't need to implement any of the video content handling. Spring Content will provide this for you. To add Spring Content to your project:
Add Spring Content to your classpath.
pom.xml
<dependency>
<groupId>com.github.paulcwarren</groupId>
<artifactId>spring-content-rest-boot-starter</artifactId>
<version>0.0.10</version>
</dependency>
<dependency>
<groupId>com.github.paulcwarren</groupId>
<artifactId>content-fs-spring-boot-starter</artifactId>
<version>0.0.10</version>
</dependency>
Associate content with your Video entity.
Video.java
#Entity
public class Video {
...
#ContentId
private String contentId;
#ContentLength
private Long contetLen;
#MimeType
private String mimeType;
...
Set up a "persistent folder" as the root of your video store. This is where uploaded videos will be stored/streamed from. Also create a VideoStore interface to describe to SC how you want to associate your content.
SpringBootApplication.java
#SpringBootApplication
public class YourSpringBootApplication {
public static void main(String[] args) {
SpringApplication.run(YourSpringBootApplication.class, args);
}
#Configuration
#EnableFilesystemStores
public static class StoreConfig {
File filesystemRoot() {
return new File("/path/to/your/videos");
}
#Bean
public FileSystemResourceLoader fsResourceLoader() throws Exception {
return new FileSystemResourceLoader(filesystemRoot().getAbsolutePath());
}
}
#StoreRestResource(path="videos")
public interface VideoStore extends ContentStore<Video,String> {
//
}
}
This is all you need to create a REST-based video service at /videos. Essentially, when your application starts, Spring Content will look at your dependencies (seeing Spring Content FS/REST), look at your VideoStore interface and inject an implementation of that interface based on the filesystem. It will also inject a controller that forwards http requests to that implementation as well. This saves you having to implement any of this yourself.
So...
POST /videos/{video-entity-id}
with a multipart/form-data request will store the video in /path/to/your/videos and associate it with the video entity whose id is video-entity-id.
GET /videos/{video-entity-id}
will fetch it again. This supports partial content requests or byte ranges; i.e. video streaming too.
and so on...support full CRUD.
There are a couple of getting started guides here. The reference guide is here. And there is a tutorial video here. The coding bit starts about 1/2 way through.
HTH
Did you enable the upload by adding the following property in the application.properties file?
## MULTIPART (MultipartProperties)
# Enable multipart uploads
spring.servlet.multipart.enabled=true
I have written an article about how to upload a multipart file in spring boot using thymeleaf. Here is the service used for the upload.
package com.uploadMultipartfile.storage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
#Service
public class FileSystemStorageService implements StorageService
{
private final Path rootLocation;
#Autowired
public FileSystemStorageService(StorageProperties properties) {
this.rootLocation = Paths.get(properties.getUploadDir()).toAbsolutePath().normalize();
try {
Files.createDirectories(this.rootLocation);
} catch (Exception ex) {
throw new StorageException("Could not create the directory where the uploaded files will be stored.", ex);
}
}
#Override
public String store(MultipartFile file)
{
// Normalize file name
String fileName = StringUtils.cleanPath(file.getOriginalFilename());
try
{
if (file.isEmpty())
{
throw new StorageException("Failed to store empty file " + file.getOriginalFilename());
}
// Copy file to the target location (Replacing existing file with the same name)
Path targetLocation = this.rootLocation.resolve(fileName);
Files.copy(file.getInputStream(), targetLocation, StandardCopyOption.REPLACE_EXISTING);
return fileName;
}
catch (IOException e)
{
throw new StorageException("Failed to store file " + file.getOriginalFilename(), e);
}
}
#Override
public void init()
{
try
{
Files.createDirectory(rootLocation);
}
catch (IOException e)
{
throw new StorageException("Could not initialize storage", e);
}
}
}
Here is a link to get the code of the application. http://mkaroune.e-monsite.com/pages/spring/spring-boot-multipart-file-upload.html

How do I redirect log4j output to my HttpServletResponse output stream?

I'm using log4j 1.2.15 in a Spring 3.1.1.RELEASE application deployed on JBoss AS 7.1.1.Final. I'm trying to route output written in log4j to my response output stream. I have output written like this
private static final Logger LOG = Logger.getLogger(TrainingSessionServiceImpl.class);


LOG.info("Creating/updating training session associated with order #:" + order.getId());
and I'm trying to route it to my output stream like so 

#RequestMapping(value = "/refreshPd", method = RequestMethod.GET)
public void refreshPD(final HttpServletResponse response) throws IOException
{
...
final WriterAppender appender = new WriterAppender(new PatternLayout("%d{ISO8601} %p - %m%n"),response.getWriter());
appender.setName("CONSOLE_APPENDER");
appender.setThreshold(org.apache.log4j.Level.DEBUG);
Logger.getRootLogger().addAppender(appender);
worker.work();
Logger.getRootLogger().removeAppender("CONSOLE_APPENDER");
but sadly, nothing is getting output to my browser, even though I know (through debugging) that logging statements are getting called. Does anyone know how I can adjust my setup to make it work? Below is my log4j.properties file, deployed to my wAR's WEB-INF/classes directory.
log4j.rootLogger=DEBUG, CA, FA
#Console Appender
log4j.appender.CA=org.apache.log4j.ConsoleAppender
log4j.appender.CA.layout=org.apache.log4j.PatternLayout
log4j.appender.CA.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
#File Appender
log4j.appender.FA=org.apache.log4j.FileAppender
log4j.appender.FA.File=/usr/java/jboss/server/default/log/log4j.log
log4j.appender.FA.layout=org.apache.log4j.PatternLayout
log4j.appender.FA.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
# Set the logger level of File Appender to WARN
log4j.appender.FA.Threshold = DEBUG
Thanks, - Dave
This was an interesting problem. The key thing is to write your own appender. I looked up the in built org.apache.log4j.ConsoleAppender code for inspiration. I have tested this in my tomcat and verified that it works. I used log4j-1.2.17 (hopefully shouldn't matter)
1) First implement your own appender. This appender will write all log events to current thread's outputstream
package com.tstwbprj.log;
import org.apache.log4j.Layout;
import org.apache.log4j.WriterAppender;
import java.io.IOException;
import java.io.OutputStream;
public class HttpLogAppender extends WriterAppender {
static ThreadLocal<OutputStream> streamPerHttpThread = new ThreadLocal<OutputStream>();
public HttpLogAppender() {
}
public HttpLogAppender(Layout layout) {
setLayout(layout); //super-class method
activateOptions();
}
public void setCurrentHttpStream(OutputStream stream) {
streamPerHttpThread.set(stream);
}
public void activateOptions() {
setWriter(createWriter(new CurrentHttpThreadOutStream()));
}
/**
* An implementation of OutputStream that redirects to the
* current http threads servlet output stream
*/
private static class CurrentHttpThreadOutStream extends OutputStream {
public CurrentHttpThreadOutStream() {
}
public void close() {
}
public void flush() throws IOException {
OutputStream stream = streamPerHttpThread.get();
if (stream != null) {
stream.flush();
}
}
public void write(final byte[] b) throws IOException {
OutputStream stream = streamPerHttpThread.get();
if (stream != null) {
stream.write(b);
}
}
public void write(final byte[] b, final int off, final int len)
throws IOException {
OutputStream stream = streamPerHttpThread.get();
if (stream != null) {
stream.write(b, off, len);
}
}
public void write(final int b) throws IOException {
OutputStream stream = streamPerHttpThread.get();
if (stream != null) {
stream.write(b);
}
}
}
}
2) Add this appender in your log4j configuration file just like the other settings
log4j.rootLogger=DEBUG, CA, FA , HA
..
log4j.appender.HA=com.tstwbprj.log.HttpLogAppender
log4j.appender.HA.layout=org.apache.log4j.PatternLayout
log4j.appender.HA.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
3) Add a small piece of code in your servlet so that this appender works correctly . Here's my servlet.
import org.apache.log4j.Category;
import org.apache.log4j.Logger;
import javax.servlet.ServletOutputStream;
import java.io.IOException;
public class LogServlet extends javax.servlet.http.HttpServlet {
private static final Logger LOG = Logger.getLogger(LogServlet.class);
protected void doPost(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, IOException {
}
protected void doGet(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, IOException {
ServletOutputStream outstream = response.getOutputStream();
configureLogForCurrentRequest(outstream);
LOG.info("Got request");//this is now send to the servlet output stream !!
LOG.info("Hello!!");
LOG.info("Done!!");
}
private void configureLogForCurrentRequest(ServletOutputStream outstream) {
HttpLogAppender appender = (HttpLogAppender) LOG.getAppender("HA");
while (appender == null) {
Category parent = LOG.getParent();
if (parent == null) {
break; //This ideally shouldn't happen. Navigated all the way to root logger and still did not find appender !!..something wrong with log4j configuration setup
}
appender = (HttpLogAppender) parent.getAppender("HA");
}
appender.setCurrentHttpStream(outstream);
}
}
Caution : This is not thoroughly tested especially with multiple servlet requests etc. Also not sure why you want to do this. Its not typical to pipe log messages to browser. Proceed with caution..:)-
Try with something like this:
Logger logger = Logger.getRootLogger();
String name = "myAppender";
Appender servletAppender = logger.getAppender(appenderName);
OutputStream out = response.getOutputStream();
if (servletAppender == null) {
servletAppender = new WriterAppender(new PatternLayout("%d{ISO8601} %p - %m%n"), out);
servletAppender.setName(appenderName);
appender.setThreshold(org.apache.log4j.Level.DEBUG);
logger.addAppender(servletAppender);
}
try {
// Your work
worker.work();
} finally {
logger.removeAppender(appenderName);
out.flush();
}
I suggest to take alternative approach and fetch log file contents to separate browser tab.
This would not require main code modification and would not destroy original page's formatting.
Some web-based log file viewers links:
http://logio.org/
http://www.log-viewer.com/net-java-log4j-log-viewer/
https://github.com/aroneous/Log4j-Log-Viewer
http://log2web.sourceforge.net/
Not a precise answer as such, but a better way that I have seen this handled is to write your own Appender that will collect logs in a ThreadLocal. At the time your servlet request completes, you can drain the contents of the ThreadLocal and output to the response stream however you wish.
This satisfies the (unstated) requirement of thread safety, and can fairly cleanly isolate the log4j (or other logging framework) implementation code (which should be small, using this technique) from the manipulation of the ThreadLocal, which could in theory be reused in other areas of your code.
This type of technique is used by many server-side scripting languages such as ColdFusion and others.
I won't go into the potential bugs you could cause with inappropriate use of ThreadLocal in an app server, there are techniques to manage this, along with relevant answers on SO and other sites.
Hope this answer might redirect your thinking in a slightly different direction!

Exception in Flying Saucer when processing images for custom UserAgentCallback

I've setup a test environment for Flying Saucer R8, and is testing building PDF's from templates and data. I'm using a custom UserAgentCallback to read external js/css and images locally from filesystem. The problem occured when introducing the following custom UserAgentCallback:
package support;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import org.xhtmlrenderer.swing.NaiveUserAgent;
public class FileUserAgent extends NaiveUserAgent {
#Override
public String resolveURI(String uri) {
URL url = null;
String path = null;
try {
url = new URL(uri);
path = url.getPath();
} catch (MalformedURLException e) {
// only path present
path = uri;
}
if (path.charAt(0) == '/') {
path = path.substring(1, path.length());
}
return path;
}
#Override
protected InputStream resolveAndOpenStream(String filepath) {
InputStream is = null;
try {
File file = new File(filepath);
is = new FileInputStream(file);
} catch (Exception e) {
System.out.print("an error occured when reading resource: " + e.getMessage());
}
return is;
}
}
The UserAgentCallback is able to read CSS without any problem, but when reading images either from src or background-image property i get the followng exception:
java.lang.ClassCastException: org.xhtmlrenderer.swing.AWTFSImage$OldAWTFSImage cannot be cast to org.xhtmlrenderer.pdf.ITextFSImage
at org.xhtmlrenderer.pdf.ITextOutputDevice.drawImage(ITextOutputDevice.java:761)
at org.xhtmlrenderer.render.AbstractOutputDevice.paintTiles(AbstractOutputDevice.java:300)
at org.xhtmlrenderer.render.AbstractOutputDevice.paintBackground0(AbstractOutputDevice.java:245)
at org.xhtmlrenderer.render.AbstractOutputDevice.paintBackground(AbstractOutputDevice.java:191)
at org.xhtmlrenderer.pdf.ITextOutputDevice.paintBackground(ITextOutputDevice.java:187)
at org.xhtmlrenderer.render.Box.paintBackground(Box.java:436)
at org.xhtmlrenderer.layout.Layer.paintBackgroundsAndBorders(Layer.java:243)
at org.xhtmlrenderer.layout.Layer.paint(Layer.java:329)
at org.xhtmlrenderer.pdf.ITextRenderer.paintPage(ITextRenderer.java:384)
at org.xhtmlrenderer.pdf.ITextRenderer.writePDF(ITextRenderer.java:348)
at org.xhtmlrenderer.pdf.ITextRenderer.createPDF(ITextRenderer.java:315)
at org.xhtmlrenderer.pdf.ITextRenderer.createPDF(ITextRenderer.java:246)
from what i read in the flying-saucer forum you have to make sure to return the right ImageResource!
what helped for me was extending the class with ITextUserAgent instead of NaiveUserAgent. (ITextUserAgent extends NaiveUserAgent).

Resources