Sunday, January 15, 2012

Carrying over Log4j context in threadpool

We use MDC feature of slf4j/log4j to automatically log the details about user/client that is making the request so we can trace the things happening in the thread. 


    public static void setRequestId(String requestId) {
        if (requestId != null) {
            MDC.put("MDC_REQUEST_ID", requestId);
        } else {
            MDC.put("MDC_REQUEST_ID", "");
        }
    }

    public static void setUserInContext(long userId) {
        MDC.put("MDC_USER_ID", String.valueOf(userId));
    }

and the log4j appender users

    <appender name="RFA_ROOT" class="org.apache.log4j.rolling.RollingFileAppender">
        <rollingPolicy class="org.apache.log4j.rolling.TimeBasedRollingPolicy">
          <param name="FileNamePattern" value="${catalina.base}/logs/kp.log.%d{yyyy-MM-dd-HH}"/>
          <param name="ActiveFileName" value="${catalina.base}/logs/kp.log"/>
        </rollingPolicy>
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%p %d{ISO8601} %t D-%X{MDC_DOMAIN} U-%X{MDC_USER_ID} S-%X{MDC_SYNC_CID} R-%X{MDC_REQUEST_ID} %c - %m%n"/>
        </layout>
    </appender>

This will automatically print User and request Id but when you want to get the user requests funnel through some threadpool to avoid thundering herd problem then the log4j context is not carried over and this breaks traceability. To solve this problem the easy solution is to just create your own Callable and use that in thread pool.

public abstract class MyCallable implements Callable {
    private Map contextMap = AppLogger.getCopyOfContextMap();
   
    @Override
    public T call() throws Exception {
        MDC.setContextMap(contextMap);
        try {
            return internalCall();
        } finally {
            MDC.clear();
        }
    }

    protected abstract T internalCall() throws Exception;

}


        MyCallable task = new MyCallable() {
            @Override
            public String internalCall() throws Exception {
...

           }
        };

and use
SpringBeanLocator.getRestRequestHelper().getRestRequestExecutors().submit(task);

This way your context would be carried over to background threads.

smartsprite to generate sprite automatically

I am a lazy person and like to automate things that can be automated. We had all our sprites hand written earlier and the problem with that approach is that it was append only. Every time you have to add a new image the developer would add it at the end but if I asked to remove an image then they would give me a look like what the heck I am talking about, they would give weeks estimate to go and fix coordinates in each and every place those images were used. Net net this seems like something that should not be that hard to automate and thats where smartsprite came to rescue. A very very handy tool to automatically generate your sprite and biggest benefit is that its non-intrusive and you can start small and keep cleaning your old code and convert to this mode as and when you change that part of code.   This is the link to the tool http://csssprites.org/

So all you need to do is add some comments at the top of  your existing css
/** sprite: kpSmartSprite; sprite-image: url('../images/kpSmartSprite-${md5}.png'); sprite-layout: vertical; */

and then the css rule where you add image reference you have to add the comment

.t-add {
        background-image: url("../images/add.png") !important;  /** sprite-ref: kpSmartSprite;  sprite-alignment: left;  sprite-margin-bottom:6px;*/
}

So you can keep adding these tags. Now you can use an ant target to generate your sprite. The tool will generate the sprite image and will also change you css file to add the coordinates.


    <target name="sprite-images" description="Creates a sprite using smartsprite by reading smart sprite annotations">
        <taskdef resource="smartsprites.xml">
          <classpath>
            <fileset dir="${thirdParty}/tools/smartsprites-0.2.8/lib">
              <include name="*.jar" />
            </fileset>
          </classpath>
        </taskdef>
        <smartsprites rootdir="${war.path}/css"
                      documentrootdir=""
                      outputdir="${war.path}/css-sprite"
                      cssfileencoding="UTF-8"
                      cssfilesuffix=""
                      loglevel="INFO"
                      spritepngdepth="AUTO"
                      spritepngie6="false" />
               <move todir="${war.path}/css" failonerror="no">
            <fileset dir="${war.path}/css-sprite" />
        </move>
</target>