Grails throws ClassNotFoundException when I setup log4j with JDBCAppender - grails

Context
I am trying to setup log4j with JDBCAppender on Grails 2.4.3, I was trying with H2, MySQL and PostgreSQL, but it throws an ClassNotFoundException exception with each driver.
Config.groovy configuration for log4j JDBCAppender:
I have tested user and password credentials information, I mean, I can connect to these databases through Datasource.groovy file.
Test1 with H2:
log4j = {
appenders {
jdbc name: "db",
databaseURL: "jdbc:h2:mem:devDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE",
user: "sa",
password: "",
driverClassName: "org.h2.Driver",
sql: "INSERT INTO event_log (log_date, log_message) VALUES ('%d{yyyy.MM.dd HH:mm:ss}', '[%-5p]. Category: %c. Message: %m. User: %X{sessionUserName} DU:[%X{sessionUserDU}]');"
}
root {
info 'grails.app.controller','db'
}
}
Test2 with Postgres:
log4j = {
appenders {
jdbc name: "db",
URL: "jdbc:postgresql://localhost/grailsTestDB",
user: "userDB",
password: "*******",
driver: "org.postgresql.Driver",
layout: pattern(conversionPattern: "[%t] %-5p %c{2} %x - %m%n - %X{username}'"),
sql: "INSERT INTO event_log (accion,nivel,req,logString,usuario) VALUES('%c{2} %x','%-5p','[%t]','%m%n','%X{username}');"
}
root {
info 'grails.app.controller','db'
}
}
Test3 with MySQL:
The next code was taken from an Grails 1.3.8 application, in that application this code works fine.
log4j = {
appenders {
appender new JDBCAppender(
name: "db",
databaseURL: "jdbc:mysql://dbdevsie.db.hostname.com/dbdevsie",
driver: "com.mysql.jdbc.Driver",
user: "dbdevsie231",
password: "********",
layout: pattern(conversionPattern: "[%t] %-5p %c{2} %x - %m%n - %X{username}'"),
sql: "INSERT INTO activity_log (accion,nivel,req,logString,usuario) VALUES('%c{2} %x','%-5p','[%t]','%m%n','%X{username}');"
)
}
root {
info 'grails.app.controller','db'
}
}
Buildconfig.groovy dependencies setup:
dependencies {
runtime 'mysql:mysql-connector-java:5.1.29'
runtime 'org.postgresql:postgresql:9.3-1101-jdbc41'
test "org.grails:grails-datastore-test-support:1.0-grails-2.4"
}
Test results:
When I test h2 it throws com.mysql.jdbc.Driver exception.
When I test Mysql it throws sun.jdbc.odbc.JdbcOdbcDriver exception.
When I test Postgresql it throws org.postgresql.Driver exception.
| Error log4j:ERROR Failed to load driver
| Error java.lang.ClassNotFoundException: sun.jdbc.odbc.JdbcOdbcDriver
| Error at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
| Error at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
| Error at java.security.AccessController.doPrivileged(Native Method)
| Error at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
| Error at java.lang.ClassLoader.loadClass(ClassLoader.java:425)
| Error at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
| Error at java.lang.ClassLoader.loadClass(ClassLoader.java:358)
| Error at java.lang.Class.forName0(Native Method)
| Error at java.lang.Class.forName(Class.java:191)
| Error at org.apache.log4j.jdbc.JDBCAppender.setDriver(JDBCAppender.java:391)
| Error at org.apache.log4j.jdbc.JDBCAppender.getConnection(JDBCAppender.java:248)
| Error at org.apache.log4j.jdbc.JDBCAppender.execute(JDBCAppender.java:215)
| Error at org.apache.log4j.jdbc.JDBCAppender.flushBuffer(JDBCAppender.java:289)
| Error at org.apache.log4j.jdbc.JDBCAppender.append(JDBCAppender.java:186)
| Error at org.apache.log4j.AppenderSkeleton.doAppend(AppenderSkeleton.java:251)
Additional comments
I also test downloading log4j 1.2.17, PostgreSQL 9.3, and MySQL 5.1.34 jar files and placed them on lib folder, but I have the same result.
Could anyone help me with this thing?
thanks in advance.
Updated
This issue shows in Mac OS, however I ran the same code in linux and there is no problem with it.
OS X Yosemite version 10.10.1
Java 1.7.0_71

You're correct in using the DSL instead of instantiating the appender inline; dependencies and classpath haven't been resolved when your code runs in Config.groovy, so you need some way to delay the actual initialization of the appender until after that happens and the DSL config is a good option.
I got this working, but had different failures than you. In my case both H2 and MySQL reliably crashed my 1.7 JVM early on, but it worked when I switched to 1.8 and now it works in 1.7 also even after deleting pretty much everything. Try getting the dependencies resolved independently by commenting out these config changes, then restarting with them enabled. This might also be due to forking - I almost always disable it by deleting the whole grails.project.fork block in BuildConfig.groovy - if nothing else works, see if that helps.
This is what I had in Config.groovy:
log4j.main = {
appenders {
jdbc name: 'jdbcAppender', driver: 'com.mysql.jdbc.Driver', user: '...',
password: '...', URL: 'jdbc:mysql://localhost/<dbname>',
layout: pattern(conversionPattern:
"insert into logs(log_date, logger, log_level, message) " +
"values('%d{yyyy-MM-dd HH:mm:ss}','%c','%p','%m')")
}
root {
info 'stdout', 'jdbcAppender'
}
error 'org.codehaus.groovy.grails',
'org.springframework',
'org.hibernate',
'net.sf.ehcache.hibernate'
}
and I created this table to log into:
create table logs (
log_date datetime not null,
log_level varchar(10) not null,
logger varchar(100) not null,
message varchar(1000) not null
) ENGINE=InnoDB;

We utilized a workaround in which we used a dataSource for the jdbc appender
Add a log datasource in Datasource.groovy:
dataSource_log {
url = "jdbc:sqlserver://DBServer:1433;databaseName=Logs;integratedSecurity=true"
dialect = "org.hibernate.dialect.SQLServerDialect"
driverClassName = "com.microsoft.sqlserver.jdbc.SQLServerDriver"
}
Then override where the JDBCAppender gets its connections
public class DataSourceAppender extends JDBCAppender {
SessionFactory sessionFactory;
protected java.sql.Connection getConnection() throws java.sql.SQLException {
SessionImplementor imp = (SessionImplementor) sessionFactory.getCurrentSession();
return imp.getJdbcConnectionAccess().obtainConnection();
}
}
And add this custom appender to log4j in Bootstrap.groovy:
def grailsApplication
def sessionFactory_log
def init = { servletContext ->
def sqlAppender = new DataSourceAppender(
grailsApplication.config.dataSourceAppender
)
sqlAppender.sessionFactory = sessionFactory_log
Logger.getRootLogger().addAppender(sqlAppender)
}
Would love to see a real fix for this though.

Related

Grails 3: Integration tests run at a development environment, not at a test environment

I have separated dataSourceConfig.yml database config file:
environments:
development:
dataSource:
dbCreate: none
url: jdbc:oracle:thin:xxxxxx
driverClassName: oracle.jdbc.OracleDriver
dialect: org.hibernate.dialect.Oracle10gDialect
username: xxxx
password: xxxx
test:
dataSource:
dbCreate: none
url: jdbc:oracle:thin:xxxxx
driverClassName: oracle.jdbc.OracleDriver
dialect: org.hibernate.dialect.Oracle10gDialect
username: xxxxx
password: xxxxx
Which I connect to the project in the Application.java:
class Application extends GrailsAutoConfiguration implements EnvironmentAware {
static void main(String[] args) {
GrailsApp.run(Application, args)
}
#Override
void setEnvironment(Environment environment) {
String configPath = environment.getProperty("local.config.location")
Resource resourceConfig = new FileSystemResource(configPath)
YamlPropertiesFactoryBean ypfb = new YamlPropertiesFactoryBean()
ypfb.setResources([resourceConfig] as Resource[])
ypfb.afterPropertiesSet()
Properties properties = ypfb.getObject()
environment.propertySources.addFirst(new PropertiesPropertySource("local.config.location", properties))
}
}
When i run integration tests via Intellij IDEA 15, it runs tests at a development environment, but the YAML config file has test section.
Is anyone knows how to fix this?
The command bellow doesn't help.
grails test test-app -integration
If you are going to run tests from the IDE you need to modify the run config to include -Dgrails.env=test. You will want to do that for the default JUnit run config so you don't have to edit every single test run config. Be aware that editing the default JUnit run config will affect all configs that are created in the future but will not update any existing configs. You may want to remove all of the existing run configs so they will be recreated with the new settings the next time you run those tests.

grails logfile does not created in production server

In my Grails project I've created and implemented log4j logging system.
In local environment file is correctly created, but in production server it is not.
here is the log4j configuration inside config.groovy file:
log4j = {
appenders {
file name: 'file', file:"/home/file.log"
}
root{
info 'stdout', 'file'
}
}
I've tried with other paths but it does not work.
How can I solve this issue?

log4j in grails: error printed when use variable in appender config

I am using slf4j "DailyRollingFileAppender" in grails 2.3.4.
When i tried to use a variable as part of the "file" parameter, the grails always print some error logs when the app starting.
But my app's log messages can printed into the specified "user-event.log" as expected although grails gives me those error messages.
Below is my log4j configuration:
log4j = {
// Example of changing the log pattern for the default console appender:
appenders {
console name: 'stdout', layout: pattern(conversionPattern: '%c{2} %m%n')
appender new DailyRollingFileAppender(
name: "userEventLog",
file: "${event.log.dir}/user-event.log",
layout: pattern(conversionPattern: '%m%n'),
datePattern: "'.'yyyy_MM_dd",
threshold: org.apache.log4j.Level.INFO
)
}
info userEventLog: "app.bean.log.UserEventLog"
}
The "event.log.dir" variable is defined as below :
environments {
development {
// event log dir
event.log.dir = "${userHome}/workspace/app/logs/event"
}
production {
// event log dir
event.log.dir = "/opt/www/app/logs/event"
}
}
The error messages printed in grails console when app starting are:
| Error log4j:ERROR Property missing when configuring log4j: event
| Error log4j:ERROR Property missing when configuring log4j: event
when i replace the "${event.log.dir}" variable with a string path, for example "/home/app/" and restart grails, the error messages disappear.
Use Holders to read config properties. I assume it will not be able to read the config property itself while setting up log4j as it is part of the config itself.
import grails.util.Holders
...
log4j = {
// Example of changing the log pattern for the default console appender:
appenders {
console name: 'stdout', layout: pattern(conversionPattern: '%c{2} %m%n')
appender new DailyRollingFileAppender(
name: "userEventLog",
file: "${Holders.config.event.log.dir}/user-event.log",
layout: pattern(conversionPattern: '%m%n'),
datePattern: "'.'yyyy_MM_dd",
threshold: org.apache.log4j.Level.INFO
)
}
info userEventLog: "app.bean.log.UserEventLog"
}

grails log4j diffrerent appenders according files

I would like to log to different appenders depending on the module...
I have 3 appenders: the console, a rolling file to log the controller and services, and another rolling file to log some stuff in jobs. I want to log the code of the job only for its rolling file and to log the controllers and services only with the other rolling file.
So this is my grails log4j configuration:
development {
def catalinaBase = System.properties.getProperty('catalina.base')
if (!catalinaBase) catalinaBase = '.'
def logDirectory = "${catalinaBase}/logs/AmbienticWebsite"
log4j = {
appenders {
console name:'stdout', layout:pattern(conversionPattern: '%c{2} [%p] - %m%n')
appender new DailyRollingFileAppender(
name: "rollingFileGrailsApp",
file: "${logDirectory}/GrailsApp.log",
datePattern: "'.'yyyy-MM-dd",
layout: pattern(conversionPattern: commonPattern)
)
appender new DailyRollingFileAppender(
name: "rollingFileImport",
file: "${logDirectory}/Imports.log",
datePattern: "'.'yyyy-MM-dd",
layout: pattern(conversionPattern: commonPattern)
)
}
root {
error 'stdout', 'rollingFileImport', 'rollingFileGrailsApp' // both stdout and AmbienticWebsite_dev.log are filled by logging information
additivity = false
}
debug rollingFileImport: 'ambienticwebsite.EventImportJob',
'time2marketing.time2marketingImportService',
'eventImportData.DiscomImportDataService',
'eventImportData.EventImportService'
info rollingFileGrailsApp: 'ambienticwebsite',
'ambienticwebsite.jobManagement.AmbienticJobListener',
'BootStrap',
'grails.app.controllers',
'grails.app.services'
}
}
With this configuration, the logs are written to the two rolling files and stdout. If I delete the rolling appenders from the root, the rolling file stays empty, even if the appenders are specified for the group of files.
Does anyone have advice to separate the log into the appenders?
I think you need to add additivity: false to the custom appenders.
debug rollingFileImport: [foo, bar, baz]
additivity: false
info rollingFileGrailsApp: [foo1, bar1, baz1]
additivity: false
Refer Logger Inheritance in grails.

grails configure log4j without rebuilding war?

This seems pretty strange, but when grails builds a war file it doesn't generate a log4j.properties or log4j.xml file.
Instead it has the following in WEB-INF/web.xml
web.xml:
<listener>
<listener-class>org.codehaus.groovy.grails.web.util.Log4jConfigListener</listener-class>
</listener>
and apparently "grails Log4j DSL configures logging in-memory". The problem here is - log4j isn't automatically exposed to JMX for us to dynamically change and there's no log4j file generated by grails. But Config.groovy is a compiled file.
There's got to be an easy way to manage this without rebuilding the war?
One option suggested is go through to spring and configure logging there:
resources.groovy:
beans = {
log4jConfigurer(org.springframework.beans.factory.config.MethodInvokingFactoryBean)
{
targetClass = "org.springframework.util.Log4jConfigurer"
targetMethod = "initLogging"
arguments = ["classpath:myapp/log4j.properties"]
}
}
then shift the configuration in the DSL to the configured file.
Can anyone advise the 'groovy' way to dynamically change logging configuration without rebuilding the WAR file each time. Using grails-1.3.7. Cutting the DSL out doesn't seem the right way.
Thanks
You may have an external config file that is searched for by your application at startup time.
You would have a MyExternalConfig.groovy file somewhere in your production environment. For example:
log4j = {
def catalinaBase = System.properties.getProperty('catalina.base')
if (!catalinaBase) catalinaBase = '.'
def logDirectory = "${catalinaBase}/logs"
appenders {
rollingFile name:"infoLog", maxFileSize:'900KB', file:"${logDirectory}/${appName}Info.log", maxBackupIndex:10, layout:pattern(conversionPattern: '%d{DATE} %p %c - %m%n'), threshold: org.apache.log4j.Level.INFO
rollingFile name:"erroLog", maxFileSize:'900KB', file:"${logDirectory}/${appName}Erro.log", maxBackupIndex:10, layout:pattern(conversionPattern: '%d{DATE} %p %c - %m%n'), threshold: org.apache.log4j.Level.ERROR
}
root {
info 'infoLog', 'erroLog'
additivity = false
}
error erroLog:"StackTrace"
error erroLog: 'org.codehaus.groovy.grails.web.servlet', // controllers
'org.codehaus.groovy.grails.web.pages', // GSP
'net.sf.ehcache.hibernate'
warn infoLog: 'org.mortbay.log'
info infoLog: "grails.app"
}
Then in your Config.groovy file, that belongs to your grails project in conf folder, you put this as the last thing of the file:
def ENV_NAME = "MY_EXTERNAL_CONFIG"
if(!grails.config.locations || !(grails.config.locations instanceof List)) {
grails.config.locations = []
}
if(System.getenv(ENV_NAME)) {
grails.config.locations << "file:" + System.getenv(ENV_NAME)
} else if(System.getProperty(ENV_NAME)) {
grails.config.locations << "file:" + System.getProperty(ENV_NAME)
} else {
println "No external configuration file defined."
}
This will look for external configurations files to add to your grails.config.locations attribute of your Config.groovy. First it looks for it as a System Environment variable (I use it this way), if it does not find, then it looks for a command line parameter (so you could add it when you start your tomcat app, as a parameter to startup.sh).
To configure your system environment variabble, just do this before starting tomcat:
MY_EXTERNAL_CONFIG="/home/tomcat/configs/MyExternalConfig.groovy"
export MY_EXTERNAL_CONFIG
--- start tomcat here ---
That's it.

Resources