class StopWatchAdvice extends AbstractServiceAdvice {

    @Around("serviceMethods()")
    public Object logMethodTime(ProceedingJoinPoint pjp) throws Throwable
    {
        // Get the target class being called.
        Class<?> targetClass = pjp.getTarget().getClass();

        // Get the target method being called.
        MethodSignature targetMethodSignature = (MethodSignature) pjp.getSignature();
        Method targetMethod = targetMethodSignature.getMethod();
        if (targetMethod.getDeclaringClass().isInterface())
        {
            // Get the underlying implementation if we are given an interface.
            targetMethod = pjp.getTarget().getClass().getMethod(pjp.getSignature().getName(), targetMethod.getParameterTypes());
        }

        // Only keep a stop watch if the class and method aren't suppressing logging and the log level is info.
        if ((AnnotationUtils.findAnnotation(targetClass, SuppressLogging.class) == null) &&
            (AnnotationUtils.findAnnotation(targetMethod, SuppressLogging.class) == null) && (LOGGER.isInfoEnabled()))
        {
            // Start the stop watch.
            StopWatch stopWatch = new StopWatch();
            stopWatch.start();

            // Proceed to the join point (i.e. call the method and let it return).
            Object returnValue = pjp.proceed();

            // Log the duration.
            long durationMilliseconds = stopWatch.getTime();
            LOGGER.info("javaMethod=\"{}.{}\" javaMethodDurationTimeInMilliseconds={} javaMethodDurationTimeFormatted=\"{}\"", targetClass.getName(),
                targetMethodSignature.getName(), durationMilliseconds, HerdDateUtils.formatDuration(durationMilliseconds));

            // Return the method return value.
            return returnValue;
        }
        else
        {
            // Invoke the method normally.
            return pjp.proceed();
        }
    }

}