Performance of log4j2 compared to log4j1 - java

I'm trying to migrate my app into using log4j2. It is currently using log4j 1.2.16. I also have a performance build for my project, and after upgrading to log4j 2, the performance seemed to have improve a lot.
That is, until I read about bridging. According to the doc, I have to exclude log4j1 JAR from the classpath, and include the bridging JAR - which I assume is named 'org.apache.logging.log4j:log4j-1.2-api'. Once I did this, performance dropped again.
So to summarise:
Performance with log4j2 + bridging jar + log4j-1.2-api + log4j1 : good
Performance with log4j2 + bridging jar + log4j-1.2-api : bad (to the point that it drops back to performance of just log4j1)
I have checked that the log4j-1.2-api is earlier in the classpath. So it should have been loaded first.
Any idea what could cause this problem?
Thank you very much in advance!
Oh my complete classpath for logging are:
org.slf4j:slf4j-api
org.slf4j:log4j-over-slf4j
org.slf4j:jcl-over-slf4j
org.apache.logging.log4j:log4j-slf4j-impl
org.apache.logging.log4j:log4j-core
org.apache.logging.log4j:log4j-api
org.apache.logging.log4j:log4j-1.2-api
log4j:log4j (with & without, as described above)
Versions:
Log4j2 : 2.6.2
slf4j: 1.7.20
log4j1: 1.2.16
My config file looks like:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration
xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="log4j2-xinclude-appenders.xml" />
<Loggers>
<Root level="info">
<AppenderRef ref="rollingFileAppender"/>
<AppenderRef ref="stdOutAppender"/>
</Root>
</Loggers>
</Configuration>
And the log4j2-xinclude-appenders.xml looks like:
<?xml version="1.0" encoding="UTF-8"?>
<appenders>
<RollingRandomAccessFile name="_rollingFileAppender" fileName="./logs/foo-${sys:app.name.suffix}.log"
filePattern="./logs/foo-${sys:foo.app.name.suffix}.log.%i">
<PatternLayout>
<Pattern>%d|%X{active.profiles}| %-5p |%X{fcp.session}|%X{StateMachine.key}|%X{StateMachine.currentState}| %m | %t | %c{1.}%n</Pattern>
</PatternLayout>
<Policies>
<OnStartupTriggeringPolicy minSize="0" />
<SizeBasedTriggeringPolicy size="100 MB" />
</Policies>
<DefaultRolloverStrategy max="10"/>
</RollingRandomAccessFile>
<Async name="rollingFileAppender" blocking="false" bufferSize="10000">
<AppenderRef ref="_rollingFileAppender"/>
</Async>
<Console name="_stdOutAppender" target="SYSTEM_OUT">
<PatternLayout pattern="%d|%X{active.profiles}| %-5p |%X{fcp.session}|%X{StateMachine.key}|%X{StateMachine.currentState}| %m | %t | %c{1.}%n"/>
</Console>
<Async name="stdOutAppender" blocking="false" bufferSize="10000">
<AppenderRef ref="_stdOutAppender"/>
</Async>
</appenders>
EDIT: This is the log4j 1 xml file that gets included in the classpath
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration debug="false" xmlns:log4j="http://jakarta.apache.org/log4j/">
<appender name="A1" class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d [%t] %-5p %c - %m%n"/>
</layout>
</appender>
<appender name="R" class="com.bar.common.util.RollingFileAppender">
<param name="File" value="./logs/bar.log"/>
<param name="MaxFileSize" value="100MB"/>
<param name="MaxBackupIndex" value="10"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d [%t] %-5p %c - %m%n"/>
</layout>
</appender>
<!-- Performance Appender -->
<appender name="OneSecondStatsAppender"
class="com.foo.perf.AggregatedStatisticsAppender">
<param name="TimeSlice" value="1000"/>
<appender-ref ref="OneSecondStatsLogger"/>
</appender>
<appender name="FiveMinuteStatsAppender"
class="com.bar.perf.DatafabricAggregatedStatisticsAppender">
<param name="TimeSlice" value="300000"/>
<appender-ref ref="FiveMinuteStatsLogger"/>
</appender>
<!-- Aggregated Performance Statistics Appender -->
<appender name="OneSecondStatsLogger" class="org.apache.log4j.FileAppender">
<param name="File" value="./logs/bar-performance.log"/>
<layout class="com.bar.perf.AggregatedStatisticsCsvLayout"/>
<filter class="com.bar.perf.CategorisedStatisticExclusionFilter"/>
</appender>
<appender name="FiveMinuteStatsLogger" class="org.apache.log4j.FileAppender">
<param name="File" value="./logs/bar-minutes.log"/>
<layout class="com.bar.perf.AggregatedStatisticsCsvLayout">
<param name="ShowEmptyStatistics" value="true"/>
</layout>
</appender>
<!-- Loggers -->
<logger name="org.perf4j.TimingLogger" additivity="false">
<level value="INFO"/>
<appender-ref ref="OneSecondStatsAppender"/>
<appender-ref ref="FiveMinuteStatsAppender"/>
</logger>
<logger name="com.bar">
<level value="INFO"/>
</logger>
<logger name="com.gemstone.gemfire">
<level value="INFO"/>
</logger>
<logger name="org.springframework.data">
<level value="INFO"/>
</logger>
<!-- Root logger configuration -->
<root>
<priority value="INFO"/>
<appender-ref ref="R"/>
</root>
</log4j:configuration>
EDIT 2:
Classpath order for poor performance:
log4j-1.2-api-2.6.2.jar
jcl-over-slf4j-1.7.20.jar
slf4j-api-1.7.20.jar
log4j-slf4j-impl-2.6.2.jar
log4j-core-2.6.2.jar
log4j-api-2.6.2.jar
log4j-1.2.16.jar
Classpath order for good performance
log4j-1.2-api-2.6.2.jar
jcl-over-slf4j-1.7.20.jar
slf4j-api-1.7.20.jar
log4j-over-slf4j-1.7.20.jar
log4j-slf4j-impl-2.6.2.jar
log4j-core-2.6.2.jar
log4j-api-2.6.2.jar
log4j-1.2.16.jar

I ran into more strange findings. I enabled '-verbose:class' JVM options to see which classes were loaded, and I can confirm that only classes from the following JARs were loaded (in the order):
slf4j-api-1.7.20.jar
log4j-slf4j-impl-2.6.2.jar
log4j-api-2.6.2.jar
log4j-core-2.6.2.jar
log4j-1.2-api-2.6.2.jar
jcl-over-slf4j-1.7.20.jar
Yet, the following two tests yield different result:
Performance test with including log4j-over-slf4j-1.7.20 & log4j-1.2.16 :
GOOD
Performance test with including log4j-over-slf4j-1.7.20 & but excluding log4j-1.2.16 : BAD
Performance test with excluding log4j-over-slf4j-1.7.20 & including log4j-1.2.16 : BAD
Note that these two JARs were not loaded at all.

I there there are two questions here:
Why does log4j 1 get used in certain classpath configurations
Why is log4j 2 not faster than log4j 1
1. Why is log4j 1 used
I suspect that the following slf4j dependencies caused the old log4j 1.2 to be used:
org.slf4j:log4j-over-slf4j
org.slf4j:jcl-over-slf4j
If you use maven these could bring in the old Log4j 1 as a transitive dependency even if you don't explicitly declare it in your POM.
Please remove these. Log4j 2 has log4j-slf4j-impl and log4j-jcl modules that will accomplish the same but use Log4j 2 instead.
You should not have Log4j 1 in the classpath. If your application (or any of the libraries you use) depend on the Log4j 1 API, then add the log4j-1.2-api module.
2. Why is log4j 2 not faster than log4j 1
The configuration you describe does not take advantage of log4j 2 features. It uses AsyncAppender (which is roughly equivalent in log4j 1 and 2) and ConsoleAppender (which is slightly worse in log4j 2). ConsoleAppender is about 60 times slower than file appender. Be extremely careful when logging to the console in production systems.
Here is what I suggest: remove the following (which now seems to give better performance, but bear with me)
log4j-over-slf4j-1.7.20
log4j-1.2.16
the old lo4j.xml configuration
Add the LMAX Disruptor dependency:
<!-- https://mvnrepository.com/artifact/com.lmax/disruptor -->
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.2.0</version>
</dependency>
Use the following log4j2.xml configuration. Temporarily just make it a simple file without includes, you can put that back later. (Notice I added <Configuration status="trace" to the beginning of the file: that will output internal log4j2 debugging statements so you can confirm that configuration completed without problems.)
Note that I made Console logging WARN level as I suspect it is impacting performance.
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="TRACE" xmlns:xi="http://www.w3.org/2001/XInclude">
<appenders>
<RollingRandomAccessFile name="_rollingFileAppender"
fileName="./logs/foo-${sys:app.name.suffix}.log"
filePattern="./logs/foo-${sys:foo.app.name.suffix}.log.%i">
<PatternLayout>
<Pattern>%d|%X{active.profiles}| %-5p |%X{fcp.session}|%X{StateMachine.key}|%X{StateMachine.currentState}| %m | %t | %c{1.}%n</Pattern>
</PatternLayout>
<Policies>
<OnStartupTriggeringPolicy minSize="0" />
<SizeBasedTriggeringPolicy size="100 MB" />
</Policies>
<DefaultRolloverStrategy max="10"/>
</RollingRandomAccessFile>
<Console name="_stdOutAppender" target="SYSTEM_OUT">
<PatternLayout pattern="%d|%X{active.profiles}| %-5p |%X{fcp.session}|%X{StateMachine.key}|%X{StateMachine.currentState}| %m | %t | %c{1.}%n"/>
</Console>
</appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="_rollingFileAppender"/>
<AppenderRef ref="_stdOutAppender" level="WARN" />
</Root>
</Loggers>
</Configuration>
Now, the final (key) point: enable log4j 2 async loggers by setting system property Log4jContextSelector to org.apache.logging.log4j.core.async.AsyncLoggerContextSelector.
This last bit should make a large performance difference. (Together with disabling Console logging.)

Related

Log4j2.xml doesn't create file suddenly in java hibernate framework

I have a maven project in NetBeans with hibernate framework. For a long time it was doing its job correctly. After some modification in database, console log is still working but writing to file has stopped without any error or exception. I have deleted log file directory and restart project, but this time it couldn't even create file path directory.
here is my log4j2.xml configuration(in src/main/resources)
I have changed path to D:\logs and D:/logs, and tried different levels (debug, info, error). Also tried to run my "Server.jar" from command line by administrator. And it should be mentioned that I tried the solution in Log4J creates log file but does not write to it and lots of other suggested solutions, but no success achieved.
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="debug">
<Properties>
<Property name="path">${sys:user.home}/logs/server</Property>
</Properties>
<Appenders >
<RollingFile name="connections" append="true" fileName="${path}/connections.log"
filePattern="${path}/connections-%d{yyyy-MM-dd}-%i.log" >
<!-- log pattern -->
<PatternLayout>
<pattern> %d{yyyy-MM-dd HH:mm:ss} [%-4level] - %msg %n</pattern>
</PatternLayout>
<!-- set file size policy -->
<Policies>
<SizeBasedTriggeringPolicy size="10 MB" />
</Policies>
<DefaultRolloverStrategy max="10"/>
</RollingFile>
<RollingFile name="unexpected-events" append="true" fileName="${path}/unexpected-events.log"
filePattern="${path}/unexpected-events-%d{yyyy-MM-dd}-%i.log" >
<!-- log pattern -->
<PatternLayout>
<pattern> %d{yyyy-MM-dd HH:mm:ss} [%-4level] - %msg %n</pattern>
</PatternLayout>
<!-- set file size policy -->
<Policies>
<SizeBasedTriggeringPolicy size="10 MB" />
</Policies>
<DefaultRolloverStrategy max="10"/>
</RollingFile>
<RollingFile name="readouts" append="true" fileName="${path}/readouts.log"
filePattern="${path}/readouts-%d{yyyy-MM-dd}-%i.log" >
<!-- log pattern -->
<PatternLayout>
<pattern> %d{yyyy-MM-dd HH:mm:ss} [%-4level] - %msg %n</pattern>
</PatternLayout>
<!-- set file size policy -->
<Policies>
<SizeBasedTriggeringPolicy size="20 MB" />
</Policies>
<DefaultRolloverStrategy max="10"/>
</RollingFile>
</Appenders>
<Loggers>
<Logger name="connections" level="info" additivity="false">
<appender-ref ref="connections" level="debug"/>
<!--<appender-ref ref="console-log" level="debug"/>-->
</Logger>
<Logger name="unexpected-events" level="info" additivity="false">
<appender-ref ref="unexpected-events" level="debug"/>
<!--<appender-ref ref="console-log" level="debug"/>-->
</Logger>
<Logger name="readouts" level="info" additivity="false">
<appender-ref ref="readouts" level="debug"/>
<!--<appender-ref ref="console-log" level="debug"/>-->
</Logger>
<Root level="error" additivity="false">
<!--<AppenderRef ref="console-log"/>-->
</Root>
</Loggers>
</Configuration>
and this is how I used it in my java class:
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
......
private static final Logger connectionsLog = LogManager.getLogger("connections");
....
connectionsLog.info("device" + deviceNumber + " disconnected");
I have seen weirdness like this when both commons-logging and jcl-over-slf4j are on the classpath together. Excluding commons-logging from the Maven classpath, anywhere it appears, often helps.
By special thanks to user944849 , I understood that the problem could be related to maven dependencies. Then I checked previous version of my project (which has correct logging) and compare dependencies and found out that a library "log4j-to-slf4j" is added (which has added because of importing "org.springframework.boot"). Then first I exclude it from dependency in pom.xml file, as a result it worked fine in NetBeans IDE but not out of it. Then I removed that dependency from pom.xml and problem solved like a charm!

Log4j: socketappender - How to capture logs in localfile when remotehost is down

I have a scenario where we are logging to remote logstash host. Now as per log4j documentation, For SocketAppender, if remote host is down, log events will be simply dropped. I would like to customize socket appender in order to capture those dropped logs in a local log file. So that nothing is missed.
I tried but was unable to.
There were 2 methods in socketappender connect and append(event)
But it seems to me, append is called only if connection is successful. Althought connect method wasn't returning anything
So how do i capture that event in localfile in case remotehost is down
Ref: https://logging.apache.org/log4j/1.2/xref/org/apache/log4j/net/SocketAppender.html
I also tried Fallbackerror handler. But it only logs once and then no more logging. So basically it doesnt seems to work
Write to a local log file and use filebeat to ship the logs to logstash. When logstash is unavailable, the logs will spool up locally (a "free" distributed cache).
There's a corner case if logstash is still down when your local log files are rotated...
You can do one thing. Use Two Appenders at a time. One Socket Appender and second File Appender. So, at a time, you logs will go both in Socket Appender to Remote Host , as well as in the file on your local.
Here is an example of this-
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<appender name="FA" class="org.apache.log4j.FileAppender">
<param name="File" value="c:/logs/log4j/log2.out"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="conversionPattern" value="%m%n"/>
</layout>
</appender>
<appender name="server" class="org.apache.log4j.net.SocketAppender">
<param name="Port" value="4712"/>
<param name="ReconnectionDelay" value="10000"/>
<param name="RemoteHost" value="localhost"/>
</appender>
<root>
<level value="DEBUG"/>
<appender-ref ref="server"/>
<appender-ref ref="FA"/>
</root>
</log4j:configuration>
You can use FA for Appending into file and Server for Remote.
UPDATE
You can try Log4j 2.7 Failover Apppender. Once, the socket Appender fails, the application will use secondary file appender.
Example-
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" name="MyApp" packages="">
<Appenders>
<RollingFile name="RollingFile" fileName="logs/app.log" filePattern="logs/app-%d{MM-dd-yyyy}.log.gz"
ignoreExceptions="false">
<PatternLayout>
<Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
</PatternLayout>
<TimeBasedTriggeringPolicy />
</RollingFile>
<Console name="STDOUT" target="SYSTEM_OUT" ignoreExceptions="false">
<PatternLayout pattern="%m%n"/>
</Console>
<Failover name="Failover" primary="RollingFile">
<Failovers>
<AppenderRef ref="Console"/>
</Failovers>
</Failover>
</Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="Failover"/>
</Root>
</Loggers>
</Configuration>

How to configure java application having 2 log4j2.xml files so that both configurations work fine

We are running a Java file named Calc.java and for this application, we are using log4j2.xml file for logging to a file named LogCalc.txt. Our Calc.java is using a .jar file named Addition.Jar, which is composed of Add.java, which also uses log4j2.xml file for its own logging to a file named LogAdd.txt.
The question here is, when we run Calc.java and access a method from Addition.Jar, logging is only happening in LogCalc.txt, the configurations in log4j2.xml of Calc.java is taken into account and log4j2.xml of LogAdd.txt is not taken into account. Due to this I am not able to get logs from Addition.jar, LogAdd.txt is empty.
How can we change our configurations such that we can see both logs from Calc.java in LogCalc.txt as well as from Add.java in LogAdd.txt, i.e., both XML configurations must work fine in our application and both of them logging to different files with their own configurations taken from respective XML files).
Our aim here is to make a project where main app uses log4j2.xml and also the included .jar in this project is using log4j2.xml, but we should get logs from both of them to their respective appenders separately without any problem.
Our Xml files look like this. log4j2.xml used in Addition.jar is as follows:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Appenders>
<File name="A1" fileName="A1.log" append="false">
<PatternLayout pattern="%t %-5p %c{2} - %m%n"/>
</File>
<Console name="STDOUT" target="SYSTEM_OUT">
<PatternLayout pattern="%d %-5p [%t] %C{2} (%F:%L) - %m%n"/>
</Console>
</Appenders>
<Loggers>
<Logger name="org.apache.log4j.xml" level="debug">
<AppenderRef ref="A1"/>
</Logger>
<Root level="warn">
<AppenderRef ref="STDOUT"/>
</Root>
</Loggers>
</Configuration>
The log4j2.xml file used with Calc.java is as follows:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Appenders>
<File name="A1" fileName="A1.log" append="false">
<PatternLayout pattern="%t %-5p %c{2} - %m%n"/>
</File>
<Console name="STDOUT" target="SYSTEM_OUT">
<PatternLayout pattern="%d %-5p [%t] %C{2} (%F:%L) - %m%n"/>
</Console>
</Appenders>
<Loggers>
<Logger name="org.apache.log4j.xml" level="debug">
<AppenderRef ref="A1"/>
</Logger>
<Root level="info">
<AppenderRef ref="A1"/>
</Root>
</Loggers>
</Configuration>
Log4j will locate the first log4j2.xml that it finds on the classpath and use that for configuration. It does not currently support multiple configurations, although work is in progress to allow it to. Even if it did it would not work the way your configuration won't work because the loggers are the same.
I suggest you review the log4j guide online. Log4j does not log on the basis of jar files but by comparing the names of the loggers you are using against what is configured. If you want everything in one jar to go to one file then use a logger name such as Calc for the first jar and a logger name of LogCalc for the second jar. Then have your configuration route the events for each of the loggers to the appropriate files.

Convert log4j 1.2 configuration related to JUL to log4j 2 configuration

I'm converting an application from log4j 1.2 to log4j2. In the log4j.properties file I found follwoing configurations which relats to Java util logging.
handlers=java.util.logging.ConsoleHandler
java.util.logging.ConsoleHandler.level=SEVERE
How can I convert this configurations to log4j2 configuration ?
Thanks!
Log4j2 provides a bridge to route all calls to the java.util.logging API to Log4j2. To activate this, set the system property java.util.logging.manager to org.apache.logging.log4j.jul.LogManager and add the Log4j2 JUL adapter jar to your classpath (see "which jars FAQ").
Then you configure log4j2 as usual. The log4j2 manual provides many example configurations.
The configuration snippet you provide might translate to something like the below (I added a FileAppender as an example).
<Configuration status="warn"><!-- use status="trace" for troubleshooting -->
<Appenders>
<Console name="STDOUT" target="SYSTEM_OUT">
<PatternLayout pattern="%m%n"/>
</Console>
<File name="FILE" fileName="myapp.log">
<PatternLayout>
<pattern>%d %p [%t] %c{1.} %m%n</pattern>
</PatternLayout>
</File>
</Appenders>
<Loggers>
<Root level="trace">
<AppenderRef ref="STDOUT" level="ERROR" />
<AppenderRef ref="FILE" />
</Root>
</Loggers>
</Configuration>

Log4j2 auto config

I have a problem applying the log4j2.xml auto configuration properly, and I think it has something to do with my folder arrangement.
I'm using maven to add log4j2 libs and arrange my projects as follows:
- one project to contain all "common" classes, used by server and client side of my system.
- another "core" project - the server side application.
Both projects use the same general package hierarchy (like com.foo.specific.package)
In the Common project I define a logger wrapper:
public class LogWrapper
{
static Logger systemParentLogger = LogManager.getLogger("com.foo");
public static Logger getLogger(Class<?> cls)
{
return LogManager.getLogger(cls.getName());
}
}
Moreover, the Common project contains the log4j2.xml file under META-INF (alongside the persistence.xml file for Hibernate usage).
<?xml version="1.0" encoding="UTF-8"?>
<configuration name="PRODUCTION" status="OFF">
<appenders>
<appender type="RollingFile"
name="MyFileAppender"
fileName="logs/app.log"
filePattern="logs/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz">
<layout type="PatternLayout" pattern="%d %p %C{1.} [%t] %m%n"/>
</appender>
</appenders>
<loggers>
<root level="error">
<appender-ref ref="MyFileAppender"/>
</root>
<logger name="com.foo" level="info" additivity="false">
<appender-ref ref="MyFileAppender"/>
</logger>
<logger name="org.hibernate" level="error">
<appender-ref ref=MyFileAppender"/>
</logger>
</loggers>
</configuration>
While running a sample code in the Core project (using the LogWrapper I wrote and some JPA voodoo), I could still see INFO hibernate logs, and no log file was created. I should state that while debugging the code, I could see that the logger fetched was given some weird value "com.foo.core.persistence.PersistenceXMLTest:ERROR in sun.misc.Launcher$AppClassLoader#2f600492"
The log4j2.xml was placed in a "Folder" which in eclipse terms is "not on classpath".
Changing META-INF to be a "source folder" solved the problem.
In addition, the log4j2.xml file was not defined properly.
These are the modifications needed:
<?xml version="1.0" encoding="UTF-8"?>
<configuration name="PRODUCTION" status="OFF">
<appenders>
<RollingFile name="MyFileAppender"
fileName="../Logs/app.log"
filePattern="../Logs/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz">
<PatternLayout>
<pattern>%d %p %C{1.} [%t] %m%n</pattern>
</PatternLayout>
<Policies>
<OnStartupTriggeringPolicy />
<TimeBasedTriggeringPolicy interval="6" modulate="true"/>
<SizeBasedTriggeringPolicy size="250 MB"/>
</Policies>
</RollingFile>
</appenders>
<loggers>
<root level="error">
<appender-ref ref="MyFileAppender"/>
</root>
<logger name="com.foo" level="info" additivity="false">
<appender-ref ref="MyFileAppender"/>
</logger>
</loggers>
</configuration>
Still couldn't make the org.hibernate logger to be redirected to my logs, but at least I got the logger to work

Categories

Resources