Wednesday Dec 04, 2013

Click History Part 2 - Access from Java

In the previous article on Click History I discussed how you can use WLST to dump or export the Click History. In this follow up I wanted to share the code you can use to actually do the same thing from Java.  This is something that you might want to do from within your applications for example to provide in-app diagnostics or from a harvesting process that goes on to archive the records 

Getting the Log Records

The fundamental task here is to dump the circular memory buffer that contains the Click History.

Setup

FIrst of all, to make the correct classes available you will need to add the "JRF Client" library into the project library list  

Code

The key imports you'll need:

import oracle.core.ojdl.reader.LogRecord;
import oracle.core.ojdl.reader.ODLTextLogReader;
import oracle.dfw.dump.DumpContext;
import oracle.dfw.dump.DumpExecutionException;
import oracle.dfw.dump.DumpManager;
import oracle.dfw.dump.DumpResult;
import oracle.dfw.dump.InvalidDumpContextException;
import oracle.dfw.framework.DiagnosticsFramework;

Here's the basic code to dump the buffer:

  DumpManager dumpMgr;
  DiagnosticsFramework df;
  try {
    df = new DiagnosticsFramework();
    dumpMgr = df.getDumpManager();
    DumpContext ctx = DumpContext.createDumpContext("odl.quicktrace");
    ctx.addArgument("handlerName", "apps-clickhistory-handler");
            //now obtain a dump
    DumpResult result;
    try {
      result = dumpMgr.executeDump(ctx);
      if (result != null) {      
        String dumpFile = result.getDumpContext().getDumpPath() +  
                          File.separator + result.getDumpFiles().get(0);
        List<ClickHistoryEntryWrapper> clicks = parseClickHistory(dumpFile);
        //Now do something with clicks....
      }
      else {
        _logger.warning("Warning: excute dump did not return a result");
      }
    } catch (InvalidDumpContextException eidc) {
      _logger.severe("Error invalid Dump Context " + eidc.getMessage());
    } catch (DumpExecutionException edee) {
      _logger.severe("Dump Execution Error " + edee.getMessage());
    }       
  } catch (Exception e) {
            _logger.severe("Error getting diagnostic framework " + e.getMessage());
  } 

The parsing function which reads the dumped file:

  private List<ClickHistoryEntryWrapper> parseClickHistory(String dumpfilename){
    ODLTextLogReader log = ODLTextLogReader.create(dumpfilename, null);    
    List<ClickHistoryEntryWrapper> clicks = new ArrayList<ClickHistoryEntryWrapper>();    
    while (true){
      LogRecord entry = log.read();
      if (entry == null){
        break;
      }
      clicks.add(new ClickHistoryEntryWrapper(entry));            
    }
        
    return clicks;
  } 

And the ClickHistoryEntryWrapper class that "decodes" the LogRecord into a format that is directly usable: 

import java.util.Date;
import java.util.Map;
import oracle.core.ojdl.reader.LogRecord;
public class ClickHistoryEntryWrapper {
  private LogRecord _entry;

  public ClickHistoryEntryWrapper(LogRecord entry) {
    _entry = entry;
  }

  public String getECID() {
    return (String)_entry.getField("EXEC_CONTEXT_UNIQUE_ID");
  }
    
  public int getECIDSeq() {
    return (Integer)_entry.getField("EXEC_CONTEXT_SEQ");
  }
    
  public String getServer() {
    return (String)_entry.getField("COMPONENT_ID");
  }

  public String getDSID() {
    return (String) ((Map) _entry.getField("SUPPL_ATTRS")).get("DSID");
  }

  public String getUserId() {
    return (String)_entry.getField("USER_ID");
  }
    
  public int getThreadId() {
    return (Integer)_entry.getField("THREAD_ID");
  }

  public String getApplication() {
    return (String) ((Map) _entry.getField("SUPPL_ATTRS")).get("APP");
  }

  public String getClickComponentType() {
    return (String) ((Map) _entry.getField("SUPPL_ATTRS")).get("CLICK_COMPTYPE");
  }

  public String getClickComponentId() {
    return (String) ((Map) _entry.getField("SUPPL_ATTRS")).get("CLICK_COMPCLIENTID");
  }

  public String getClickViewId() {
    return (String) ((Map) _entry.getField("SUPPL_ATTRS")).get("CLICK_VIEWID");
  }

  public String getClickRegionViewId() {
    return (String) ((Map) _entry.getField("SUPPL_ATTRS")).get("CLICK_REGIONVIEWID");
  }

  public Date getClickTime() {
    return new Date(Long.parseLong((String)
              ((Map)_entry.getField("SUPPL_ATTRS")).get("CLICK_STARTTIME")));
  }

  public String getClickType() {
    return (String) ((Map) _entry.getField("SUPPL_ATTRS")).get("CLICK_TYPE");
  }

  public String getClickDescription() {
    return (String) ((Map) _entry.getField("SUPPL_ATTRS")).get("CLICK_COMPDISPLAYNAME");
  }

  public String getClickRegionViewName() {
    return (String) ((Map) _entry.getField("SUPPL_ATTRS")).get("CLICK_REGIONVIEWNAME");
  }
}

Tuesday Dec 03, 2013

Click History in ADF 12c

In previous posts (see here) I've covered various aspects of logging in ADF and in conjunction with a presentation delivered at the UK Oracle Users Group, I'm continuing the trend with an article on the new Click History functionality within 12c. 

So What is Click History? 

Basically the feature relates to instrumentation that is built into the ADF framework which, when switched on, continuously reports the actions that users have taken within the UI.  This mostly relates to physical clicks on UI elements such as buttons and tabs, but it also reports scrolling events, graph draws and more.  Why is it useful?  Well the point is that it can help answer that particular question - "What did you do before you got the error?" You can see how you could leverage this in a feedback or error reporting function in a UI where the information is extracted and automatically bundled with other useful information for the Support staff to have a look at. 

Each Click History record provides information about the  component, region and view that reported the event, along with key information about the application,  the user and the DSID (Diagnostic Session ID).

Here's a screen shot from some in-application diagnostics that I've developed as an small example.  In this case the history is being dumped and read into the actual application UI.  This is something I'll cover doing in a follow up article. 

Click History Data

Enabling Click History 

As I mentioned Click History is part of the framework in 12c, however, it's not switched on for every application by default.  There will be a performance overhead for any kind of tracing like this so we want to be explicit about choosing to use it. 

In your application itself you need to make 2 small changes:

  1. Create a new web.xml context parameter with the name oracle.adf.view.faces.context.ENABLE_ADF_EXECUTION_CONTEXT_PROVIDER and set the value to true.
  2. Add an extra library reference to your /WEB-INF/ weblogic.xml file (create this if you don't have one already).  Here's the reference you need:

     <library-ref>
       <library-name>odl.clickhistory.webapp</library-name>
     </library-ref> 

Assuming that you have a correctly configured Weblogic domain that has been extended with JRF this library will already be defined for you.  

So in your application, that's basically it. However, we then need to do a little work on the server to configure the server side parameters for this, specifically how much space should be devoted to the Click History buffer.  To explain, Click History has a logical circular buffer of a specified size. As the buffer fills up then new records will be written from the start again and the older records discarded. When you want to look at the history, then you have to dump this buffer, but we'll come to that later. 

Server Side Configuration of Click History 

Switching Click History On or Off

 Although individual applications enable Click History, the server also has to be told to pay attention.  This is very simple to do as it is controlled by switching a particular logger (oracle.clickhistory.EUM) to the NOTIFICATION level. This can be done from Enterprise Manager, or from the Weblogic scripting tool, WLST. Here, I'll show you how to do it from the command line using WLST, I think you can probably work out how to do it though EM yourself. 

So, with WLST running:

wlst> connect()
wlst> getLogLevel(logger='oracle.clickhistory.EUM')
INCIDENT_ERROR:1

The value of INCIDENT_ERROR indicates that the facility is OFF, so we can set it on with the following command:

wlst> setLogLevel(logger="oracle.clickhistory.EUM",
                  target="AdminServer",level="INFO", 
                  persist="1")

If you've not encountered the setLogLevel command in WLST before, just issue help('setLogLevel') from the WLST command line to view the options and what they mean. 

Viewing the Click History Buffer

Unlike a normal logging message we can't view the Click History output directly in EM, instead we need to explicitly dump the buffer to the console, or more usually a file.  As I mentioned we can also do this in code which is something I'll cover in another article. So for now, let's look at how to do that from WLST:

wlst>executeDump(name="odl.quicktrace", 
                 outputFile="/home/oracle/ch_dump.out")

Again you can issue the help() command in WLST for more information about the options on executeDump(), but in this case I'm just taking the simple option of dumping the data to a file.. Inside the file you'll see the output containing all of the lines of  Click History information like this:

[2013-11-20T09:08:53.332+00:00] [AdminServer] [NOTIFICATION] [] [oracle.clickhistory.EUM] 
[tid: 120][userId: alice] [ecid: 67ea61aa-dde8-4e35-afa2-a75a24c8a820-00001769,0] 
[CLICK_COMPTYPE: oracle.adf.RichShowDetailItem] [APP: SummitProductionDeploy] 
[DSID: 0000K9ohwuNFw000jzwkno1IYTfR00000X] [CLICK_VIEWID: /index] 
[CLICK_REGIONVIEWID: /customer-task-flow-definition/Customers] [CLICK_STARTTIME: 1384938532808] 
[CLICK_RENDERTYPE: oracle.adf.rich.Item] [CLICK_TYPE: disclosure] 
[CLICK_COMPCLIENTID: pt1:r1:0:sdi2] [CLICK_WINDOWID: w0] [CLICK_REGIONVIEWNAME: ] 
[CLICK_COMPDISPLAYNAME: Orders Dashboard] 

 It's pretty easy to parse this by eye...

Configuring the Click History Buffer Size

 You can view the initial size of the circular buffer by issuing the following WLST command:

wlst> listLogHandlers(name="apps-clickhistory-handler")

This will return a chunk of information, including the all important buffer size. (some information omitted here for space reasons):

Handler Name: apps-clickhistory-handler
type:         oracle.core.ojdl.logging.QuickTraceHandlerFactory
useLoggingContext: true
encoding:	    UTF-8
bufferSize:	    880640
…
flushOnDump:	    false

To change the buffer size from the value of 880640bytes it's another WLST command:

wlst> configureLogHandler(name="apps-clickhistory-handler",
                          propertyName="bufferSize",
                          propertyValue="1048576")

So that's the basics of Click History.  Next time I'll look at how to dump the buffer and use it in your application code directly. 

Tuesday May 31, 2011

Adventures in ADF Logging - Part 2

Logging Templates

OK last time I said I'd tell you about how to look at the logging output next, but then I got all enthusiastic this morning and thought I'd create some code templates to help you use the ADFLogger. Code templates are a really neat feature of JDeveloper and if there is some bit of code (like logging) that you use a lot then 5 minutes spent building a template can save you a bunch of time in the long run.

Here are the templates I've created:

Shortcut Purpose
lgdef A basic static class logger definition
lgdefr A basic static class logger definition with resource bundle
lgdefp A basic static package logger definition
lgi Log statement for an informational message
lgc Log statement for configuration information
lgw Log statement for a warning message
lgs Log statement for an error message
lgig Guarded log statement for an informational message
lgcg Guarded log statement for configuration information
lgwg Guarded log statement for a warning message
lgsg Guarded log statement for an error message

Installing the Templates

I've made these templates available as an XML export that you can download from here: loggingTemplates.xml

To install these:

  1. Open  Tools > Preferences from the JDeveloper menu
  2. Expand the Code Editor  > Code Templates node in the preferences navigator
  3. Select the More Actions  > Import menu option as shown here and import the xml file.

Logging import

Friday May 27, 2011

Adventures in ADF Logging - Part 1

I see a lot of ADF code from both internal and external users of the framework and one thing that strikes me is how underused the ADFLogger is. There are a fair few blog articles in the community about the ADFLogger already, but they mostly repeat the basics and I wanted to go a little deeper here. 

The ADFLogger is a logging mechanism which is built into the ADF framework. It's basically  a thin wrapper around the java.util.Logging APIs with a few convenience methods thrown in and, most importantly some specific features integrated  into both JDeveloper and Enterprise Manager. All in all it's preferable to use this built-in logger for these reasons, plus it can help you avoid the kind of class-loading issues if you picked up some random version of something like Log4J.

Using the ADF Logger 

At it's most basic you create and use a one of these loggers like this:

First define the logger itself - usually a static variable in a particular class - for example one of your managed beans:

    private static ADFLogger _logger = 
            ADFLogger.createADFLogger(MainPageBackingBean.class); 

You'll notice here that the argument to the createLogger() function is the class. It can also be simply an identifying name or even a Java Package (check out the JavaDoc to learn more). By using a class reference here we are able to refine the logging output to focus very specifically on what's happening in this class. There is nothing wrong, however, in say defining a package level logger if you don't need that level of granularity / control. Indeed to probably want to define a at least a parent logger at the top level of the hierarchy of  loggers within your namespace (package structure) so that you can simply define a resource bundle for all the loggers to share. 

    private static ADFLogger _logger = 
                 ADFLogger.createADFLogger(
                              Package.getPackage("oracle.demo.whippet"),
                              "oracle.demo.whippet.LoggingResBundle"); 

This resource bundle will then* be inherited by any logger in the same hierarchy. Note also that the createADFLogger()  function will create the logger instance for you, or, in this kind of scenario return one that already exists.

*OK well actually no. That should happen but in my testing it's actually not working correctly in Patchset 4. So for now if you want to associate a resource bundle with the logger do so at the level of the logger you are using to log rather than some parent.

Next, throughout your code you can sprinkle logging statements at various levels which are (I hope) fairly self explanitory, for example in this constructor:

    public MainPageBackingBean() {
        super();
        _logger.info("Creating a new instance");
        if (BindingContext.getCurrent() == null) {
            _logger.warning("Injected Data-Binding Reference is null");
        } else {
            try {
                doSomethingComplex();
            } catch
            weirdAppException waex;
            {
                _logger.severe("Unexpected exception doing complex thing",
                               waex);
            }
        }
    }

As you can see we generally just pass a String message to the logger, although in the error case we can pass a throwable exception as well.  As I alluded to earlier you can also grab your Strings from a resource bundle, rather than hardcoding them. This probably makes sense for error messages but may be overkill for programmers eyes only messages. To grab the resource bundle you simply call the getFormattedMessage() function on the logger, or you can also get hold of the resource bundle that it references using getResourceBundle().

    _logger.severe(_logger.getFormattedMessage("errors.db_connection")); 

Where the resource  bundle contains something like:

   errors.db_connection=Unable to establish connection with database!

You can pass parameters to inject into the resource string as well 

The final thing I wanted to mention in this posting was the use of guard conditions. Although logging calls themselves are cheap if logging is not enabled, you may actually need to do quite a lot of work to prepare the stuff you want to log. For example, I have a standardized method that I use as a Task Flow Initializer to dump out the contents of the PageFlowScope for that taskflow.  That's a pretty expensive operation so you want to bypass that whole thing if you know that every single log message within it will be ignored.

Therefore you can use the  isLoggable() method to wrap the whole thing. Here's the example:

    public void diagnosticInitializer() {

        //Only do the work if we need to
        if (_logger.isLoggable(Level.INFO)) {
            AdfFacesContext actx = AdfFacesContext.getCurrentInstance();
            FacesContext fctx = FacesContext.getCurrentInstance();
            //Gather key information to dump
            String windowName =  actx.getWindowIdProvider().getCurrentWindowId(fctx);
            String viewPort = ControllerState.getInstance().getCurrentViewPort().getClientId();
            Map pageflowScopeMap = actx.getPageFlowScope();

            _logger.info("TaskFlow Diagnostics for " + viewPort);

            if (!pageflowScopeMap.isEmpty()) {
                Iterator mapIter = pageflowScopeMap.entrySet().iterator();
                while (mapIter.hasNext()) {
                    Map.Entry entry = (Map.Entry)mapIter.next();
                    String varKey = entry.getKey().toString();
                    Object varValue = entry.getValue();
                    String varClass = "n/a";
                    if (varValue != null){
                        varClass = varValue.getClass().getName();
                    }
                    StringBuilder bldr = new StringBuilder();
                    Formatter formatter = new Formatter(bldr, Locale.US);
                    formatter.format("Key:\"%s\" Value:\"%s\" Type:[%s]", varKey, varValue, varClass);                    
                    _logger.info(bldr.toString());
                }
            } else {
                _logger.info("No PageFlowScope associated with this TaskFlow instance");
            }
        }
    }

Thats all for now, next time we'll look at how you can actually view these log messages. 

About

Hawaii, Yes! Duncan has been around Oracle technology way too long but occasionally has interesting things to say. He works in the Development Tools Division at Oracle, but you guessed that right? In his spare time he contributes to the Hudson CI Server Project at Eclipse
Follow DuncanMills on Twitter

Note that comments on this blog are moderated so (1) There may be a delay before it gets published (2) I reserve the right to ignore silly questions and comment spam is not tolerated - it gets deleted so don't even bother, we all have better things to do with our lives.
However, don't be put off, I want to hear what you have to say!

Search

Archives
« April 2014
MonTueWedThuFriSatSun
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
    
       
Today