Friday, June 5, 2009

Custom Logging in Java Layer in Oracle Applications R12

Hi All,
I have often felt for years simple SOP (System.out.Println) is the best way of debugging in java/j2ee applications. I have the evil habit.. of putting lots of SOPs while writing code for OAF or ADF or simple j2ee applications. I generally create a method with sop in it eg:

private void putLog(Object o)
{
System.out.println(o);
}

and call it with the code wherever required. The benefit is at the point of deployment of code, i just need to change this method from sop to writeDiagnostics().The worst part of diagnostic logging is when you enable logging on the Applications... "Show Log On Screen", it shows so many logs apart from my custom log statements, that its really difficult to debug.

The other standard way of debugging java code on server is using OACore log in OC4J server.In R12,
Goto $ORA_CONFIG_HOME/10.1.3/opmn/conf
take the backup of opmn.xml
edit opmn.xml for data id="java-options" and add the following:
-DAFLOG_ENABLED=true -DAFLOG_LEVEL=statement
-DAFLOG_MODULE=fnd%
-DAFLOG_FILENAME=/tmp/aflog.txt -Djbo.debugoutput=console

The log message should get written in,
$INST_TOP/logs/ora/10.1.3/opmn/oacore_default_group_1/oacorestd.out

But for this my code should contain SOPs, which is also a problem, because I don't wanna SOPs in my code after deployment on server, as SOPs have huge impact on performance.

So, I need a way (probably a magic .... :) )that my debug statements become SOPs when I run my code in jdeveloper automatically, and when I run the same code in server, these statements should go in a text file if I enable logging on server, lets' say we make it dependent on a profile , so that I can also disable these statements/logging in production just by changing profile value.

Ok so make this magic or logic happen I used a trick and have written a custom logger class with static methods, let see how this class works.Ok for making this logging happen follow these steps:

STEP1
-------------
Copy this class and put in any pkg under custom directory in java top, like I have used package xx.oracle.apps.ak.logging. Replace this pkg name with the pkg u want to put this class in.

package xx.oracle.apps.ak.logging;

import java.awt.Toolkit;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import oracle.apps.fnd.framework.OAException;
import oracle.apps.fnd.framework.server.OADBTransaction;
import oracle.apps.fnd.framework.webui.OAPageContext;
import java.io.PrintWriter;
import oracle.apps.fnd.common.WebAppsContext;
public class XxLogger
{
public XxLogger()
{
}

public static void putLog(OADBTransaction ot, Object o)
{
printLog(ot, null, null, null, o);
}

public static void putLog(OAPageContext pageContext, Object o)
{
printLog(null, null, pageContext, null, o);

}

public static void putLog(WebAppsContext ctx, Object o)
{
printLog(null, ctx, null, null, o);

}

public static void printLog(OADBTransaction ot, WebAppsContext ctx,
OAPageContext pageContext, Exception exp,
Object o)
{
FileOutputStream outFile = null;
try
{
//this line should throw headless exception on server
//due to opmn.xml configuration for oc4j node.
//and would work fine in local jdev
Toolkit.getDefaultToolkit().getScreenSize();
if (o == null)
{
exp.printStackTrace();
}
else
{
System.out.println(o);
}
}
catch (Exception e)
{
try
{
String profile = null;
if (ot != null)
{
profile = ot.getProfile("XX_LOG");
}
else if (pageContext != null)
{
profile = pageContext.getProfile("XX_LOG");
}
else if (ctx != null)
{
profile = ctx.getProfileStore().getProfile("XX_LOG");
}
if ((profile == null) || ("".equals(profile)))
{
//No logging

}
else
{
outFile = new FileOutputStream(profile + "/xx_log.log", true);
PrintStream ps = new PrintStream(outFile);

if (exp != null)
{
ps.append(new java.util.Date().toString() + " : " +
"*************************************Exception Details*****************************");
ps.append(" \n ");
exp.printStackTrace(ps);
ps.append("*************************************Exception Details End *****************************");

}
else
{
ps.append(new java.util.Date().toString() + " : " + o);
}
ps.append(" \n ");
}
}
catch (FileNotFoundException f)
{
// TODO
e.printStackTrace();
}
}
}


public static void printStack(OADBTransaction ot, Exception e)
{
printLog(ot, null, null, e, null);
}


public static void printStack(WebAppsContext ctx, Exception e)
{
printLog(null, ctx, null, e, null);
}


public static void printStack(OAPageContext pageContext, Exception e)
{
printLog(null, null, pageContext, e, null);
}

}

STEP2
----------
Now create a custom profile which will store path of log file we want to get generated,please note if this profile value is null then no log will be generated.Replace the profile name with "XX_LOG" in the code at all 3 places.

STEP3
========
set the profile value to the path where you wanna log file to be generated.Also please note you would have to manually create "xx_log.log" file there for the first time(for that just create a simple fie and rename as "xx_log.log"), this I have done purposely in code, so that even if by mistake profile value is not null in production, no log should be generated.

STEP 4
===========
Use this logging in OAF/JTT/JTF/JSP file :

In CO
========
XxLogger.putLog(OAPageContext pageContext, Object o)

In AM,VO,EO
============
XxLogger.putLog(OADBTransaction ot, Object o)


In JTT/JTF
============
import oracle.apps.fnd.common.WebRequestUtil;
import oracle.apps.fnd.common.WebAppsContext;
WebAppsContext ctx = WebRequestUtil.validateContext(request, response);
XxLogger.putLog(WebAppsContext ctx, Object o)

STEP 5
===========
For printing stacktrace of exceptions :


In CO
========
XxLogger.printStack(OAPageContext pageContext, Exception e)

In AM,VO,EO
============
XxLogger.printStack(OADBTransaction ot, Exception e)


In JTT/JTF
============
import oracle.apps.fnd.common.WebRequestUtil;
import oracle.apps.fnd.common.WebAppsContext;
WebAppsContext ctx = WebRequestUtil.validateContext(request, response);
XxLogger.printStack(WebAppsContext ctx, Exception e)


We are done the log would now be generated at the position marked in profile and the these messages will automatically converted to SOPs when you run your code in local jdev

Advantages of this logging
=============================
1) You don't need to change your code again and again while working in local or deployed on server.
2) If the development is done in a team, everybody in the team can set the profile at his/her user level so that different log is generated for each user.
3)This code when run in jdeveloper without any change all putLog() and printStack() statements will convert into System.out.println.
4) On server if profile value is set to null , no log is generated, else if some location is set, log is generated there.
5) Same class can be used in all OAF AND JSP components.
6) All methods putLog and printStack are static, so you need not instanciate the class, direct call of method will work.
7) Sometimes while working at offshore, you don't have access to DBA, who can enable log.... :) this is my personal one... u might not face it.

Logic of how code works
=========================
If you have read the code of class , in all the methods code essentially calls printLog method, which has the first line as

Toolkit.getDefaultToolkit().getScreenSize();
This line throws headless exception on server mainly because opmn.xml file configuration by default throws headless exception if any JAVA awt api is called, but this line works fine in jdev, so we utilise this logic to identify whether the code is running on server or jdev. Accordingly it writes the statements in a file, if file is enabled or else calls SOP in jdev.

2 comments:

Pankaj said...

Nice Article on log management

Unknown said...

Hi,
This is Nageshwar

Please help on this issue...please or send an email the solution to my email id -OMSHIVANAGESH@GMAIL.COM

one more question i am looking for answer...


I am getting below error when i am trying to run the page from J-developer(10.1.3.5.0.3) version.(we are doing for R12.2.4 Version).


java.io.IOException
at oracle.apps.fnd.common.WebRequestUtil.validateContextPrivate(WebRequestUtil.java:559)

Thanks and Regards,
Nageshwar Reddy Porla.