logging-log4j-user mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From "Prastein, Rebeccah H" <Rebeccah.H.Prast...@questdiagnostics.com>
Subject I/O blocking? buffering?
Date Wed, 10 Dec 2008 19:37:05 GMT
I have a web application with two servlets, a Spring MVC-based
WebCalculatorController servlet and a servlet named Log4JServlet that I
use to change the logging level dynamically.  I configure log4j
programmatically in the Log4JServlet (no properties file or xml file).
The Log4JServlet is configured to load first on startup, in web.xml.  I
have two log files, one for debug and one to log web calculator request
input and output.  I'm using Spring 2.5, which includes log4j 1.2.14.
 
The problem is that my requestLogger log messages are not getting
written out, usually until I call the initialize() method in my
Log4JServlet or I restart the web application.  What both scenarios have
in common is that I removeAllAppenders() from the requestLogger.  I
think from a previous session of experimenting and testing (I've been
struggling with this for over a week), that if I made a bunch of calls
to the web calculator, eventually all of the backlogged messages would
get written, but it wasn't consistent as to the number of requests or
the length of time it took to call them.  I also have some issues with
the debug log messages occasionally not getting written, but this is
much less frequently a problem, and definitely frees up with additional
web calculator requests.  I don't remember for sure about the
requestLogger messages.  The debug log is currently more verbose than
the request log, so maybe it's a buffering thing.
 
I can't figure out what I am doing wrong.  I'm using
RollingFileAppenders to write to the log files.  The are supposed to
default to flush their writers at the end of each line, but I tried both
setting buffer size to something small and explicitly setting
immediateFlush to true anyway, and it made no difference.  I don't have
any synchronization code involved that I can think of or see, and the
methods that produce the strings I'm logging don't themselves call
log4j.  The test log messages that I write within my initialize() method
and my init() method in Log4JServlet write out just fine.  It's only the
ones in the WebCalculatorController servlet getting held up.
 
I do use Spring dependency injection to configure log4j (to determine
whether to use RollingFileAppenders or DailyRollingFileAppenders,
maxBackupIndex, maxFileSize, and the log level to use at startup); don't
know whether or not that has any bearing on the problem.  
 
I've written a little utility to write out all of the current appenders
that I call after my web calculator logging call, and I don't seem to
have any duplicate appenders (that I can see, anyway).  Could there be
duplicates that I *can't* see by enumerating all of the LogManager's
Loggers and each Logger's Appenders?
 
My web application container is Tomcat 5.5, running as a service on a
test machine.  I'm probably going to need to run it locally as a
standalone, so I can set the debug flag.  Any other suggestions how to
figure out wtf is going on?
 
Thanks,
 
Rebeccah
 
 
package com.quest.nichols.genetics_rd.log4j;
 
import java.io.*;
import java.util.Enumeration;
import javax.servlet.*;
import javax.servlet.http.*;
 
import org.apache.log4j.*;
import org.springframework.web.context.WebApplicationContext;
import
org.springframework.web.context.support.WebApplicationContextUtils;

/**
 * <p>Title: MSS Instant Risk Web Calculator</p>
 * <p>Description: Calculate revised first trimester DS and Tri18 risks
after NT</p>
 * <p>Copyright: Copyright (c) 2008</p>
 * <p>Company: Quest Diagnostics</p>
 * @author Rebeccah H. Prastein
 * @version 1.0
 */
public class Log4JServlet
    extends HttpServlet
{
    public static final String REQUEST_LOG = "requestLogger";
    protected final Logger LOGGER =
Logger.getLogger(Log4JServlet.class);
 
    private static Log4JConfig config = null;
    private static String debugLogFilename = "logs" + File.separator + 
        "webcalculator" + File.separator + "webcalculator_debug.log";
    private static String requestLogFilename = "logs" + File.separator +

        "webcalculator" + File.separator + "webcalculator_requests.log";
    
    private static final String CONTENT_TYPE = "text/html";
 
    public static final String RESET_PARAM = "reset";
    public static final String LOGGER_PARAM = "logger";
    public static final String LOGGER2_PARAM = "logger2";
    public static final String LEVEL_PARAM = "level";
    public static final String CONSOLE_PARAM = "console";
 
    public static final String TRUE = "true";
    public static final String FALSE = "false";
    public static final String ROOTLOGGER = "rootLogger";
    public static final String REQUESTLOGGER = "requestLogger";
    public static final String NOLOGGER = "noLogger";
    public static final String OFF = "off";
    public static final String FATAL = "fatal";
    public static final String ERROR = "error";
    public static final String WARN = "warn";
    public static final String INFO = "info";
    public static final String DEBUG = "debug";
    public static final String TRACE = "trace";
    public static final String ALL = "all";
 
    //Initialize global variables.
    //Note:  This overrides log4j.properties!!!
    //10/27/08 call to initialize() MOVED to Log4JServletDefaultStartup,

    //so that whether or not to use the hard-coded configuration can be 
    //configured in web.xml.
    //11/18/08:  Since any use of the Log4J servlet to reset would go
back
    //to the settings configured in the code, these are no longer
hard-coded.
    //Instead, it's configured using Spring dependency injection in 
    //applicationContext.xml.  Log4JServletDefaultStartup is gone.
    //So now, the only reason to use the log4j.properties is if the
Log4JServlet
    //is completely disabled (not loaded at startup, and no servlet
mapping 
    //points to it).
    public void init() throws ServletException
    {
        
        WebApplicationContext ctx = 
 
WebApplicationContextUtils.getRequiredWebApplicationContext(
                this.getServletContext());
        config = (Log4JConfig)ctx.getBean("log4j");
 
        //The Log4J initialization can done in some
        //Listener class definition,
        //if that is what is loaded first.
 
        initialize();
 
        getRequestLogger().info("Done with init");
        getRootLogger().info("Done with init");
    }
 
    /**
     * initialize
     * This replaces the init() method, so that it can be called from
     * some Listener class, instead of at servlet startup, in case the
     * Listener is initialized before this servlet is loaded.
     *
     * @throws ServletException
     */
    public void initialize()
    {
        //Initialize the debug log file appender
        try
        {
            if (getRootLogger().getAllAppenders().hasMoreElements())
            {
                System.out.println("Initializing -- removing all
rootLogger appenders");
                getRootLogger().removeAllAppenders();
            }
 
            PatternLayout patternLayout = new PatternLayout(
                            "%d{yyyy-MM-dd HH:mm:ss z} [%p] (%c) %m%n");
 
            DailyRollingFileAppender fileAppender = new
                    DailyRollingFileAppender(patternLayout, 
                    debugLogFilename, "'.'yyyy-MM-dd-HH");
            fileAppender.setBufferSize(config.getBufferSize());
            fileAppender.setImmediateFlush(true);
            fileAppender.setName("rootLoggerDailyRollingFileAppender");
 
            RollingFileAppender fileAppender2 = new 
                    RollingFileAppender(patternLayout,
debugLogFilename);
            fileAppender2.setMaxFileSize(config.getMaxFileSize());
            fileAppender2.setMaxBackupIndex(config.getMaxBackupIndex());
            fileAppender2.setBufferSize(config.getBufferSize());
            fileAppender2.setImmediateFlush(true);
            fileAppender2.setName("rootLoggerRollingFileAppender");
 
            if (config.getRollingLogType().equalsIgnoreCase("daily"))
            {
                fileAppender.activateOptions();
                BasicConfigurator.configure(fileAppender);
            }
            else if
(config.getRollingLogType().equalsIgnoreCase("size"))
            {
                fileAppender2.activateOptions();
                BasicConfigurator.configure(fileAppender2);
            }
            else
            {
                throw new RuntimeException("Unable to initialize log4j:
" +
                        "rollingLogType must be daily or size");
            }
            System.out.println("Log4J rootLogger initialized\n");
        }
        catch (IOException ex1)
        {
            System.out.println("Error adding debug log fileAppender " +
                    "- Log4J could not be initialized");
            ex1.printStackTrace();
        }
 
Logger.getRootLogger().setLevel(Level.toLevel(config.getStartupLogLevel(
)));
 
        //Initialize the request log file appender
        try
        {
            if (getRequestLogger().getAllAppenders().hasMoreElements())
            {
                System.out.println("Initializing -- removing all
requestLogger appenders");
                    getRequestLogger().removeAllAppenders();
            }
            else
            {
                System.out.println("requestLogger has no appenders
yet.");
            }
 
            PatternLayout patternLayout = new
CalculatorCSVPatternLayout(
                            "%d{MMM dd, yyyy HH:mm:ss z},%m%n");
 
            if (config.getRollingLogType().equalsIgnoreCase("daily"))
            {
                getRootLogger().info("About to instantiate appender for
requestLogger");
                DailyRollingFileAppender fileAppender = new
                        DailyRollingFileAppender(patternLayout, 
                        requestLogFilename, "'.'yyyy-MM-dd-HH");
                getRootLogger().info("Appender for requestLogger
instantiated");
                fileAppender.setBufferSize(config.getBufferSize());
                fileAppender.setImmediateFlush(true);
 
fileAppender.setName("requestLoggerDailyRollingFileAppender");
                getRootLogger().info("Options set for appender for
requestLogger");
                getRequestLogger().addAppender(fileAppender);
                getRequestLogger().info("Just added daily rolling file
appender to requestLogger");
                fileAppender.activateOptions();
                getRequestLogger().info("Appender options activated");
            }
            else if
(config.getRollingLogType().equalsIgnoreCase("size"))
            {
                getRootLogger().info("About to instantiate appender2 for
requestLogger");
                RollingFileAppender fileAppender2 = new 
                        RollingFileAppender(patternLayout, 
                        requestLogFilename);
                getRootLogger().info("Appender2 for requestLogger
instantiated");
                fileAppender2.setMaxFileSize(config.getMaxFileSize());
 
fileAppender2.setMaxBackupIndex(config.getMaxBackupIndex());
                fileAppender2.setBufferSize(config.getBufferSize());
                fileAppender2.setImmediateFlush(true);
 
fileAppender2.setName("requestLoggerRollingFileAppender");
                getRootLogger().info("Options set for appender2 for
requestLogger");
                getRequestLogger().addAppender(fileAppender2);
                getRequestLogger().info("Just added rolling file
appender to requestLogger");
                fileAppender2.activateOptions();
                getRequestLogger().info("Appender options activated");
            }
            else
            {
                throw new RuntimeException("Unable to initialize log4j:
" +
                        "rollingLogType must be daily or size");
            }
            getRootLogger().info("About to set requestLogger additivity
to false.");
            getRequestLogger().setAdditivity(false);
            System.out.println("Log4J requestLogger initialized\n");
        }
        catch (IOException ex)
        {
            //If the log file can't be accessed, don't just write to the
debug log --
            //Throw an exception and notify the sysadmin by printing to
the console.
            ex.printStackTrace();
            //REQUEST_LOGGER = null;
            throw new Error("Unable to add fileAppender to request
Logger",ex);
        }
 
getRequestLogger().setLevel(Level.toLevel(config.getStartupLogLevel()));
        getRequestLogger().info("Level has been set to " +
config.getStartupLogLevel());
        System.out.println("Done initializing...");
        System.out.println("Exiting initialize().");
    }
 
    /**
     * doGet
     * Process the HTTP Get request.
     *
     * @param request HttpServletRequest
     * @param response HttpServletResponse
     * @throws ServletException
     * @throws IOException
     */
    public void doGet(HttpServletRequest request, HttpServletResponse
response)
            throws ServletException, IOException
    {
        response.setContentType(CONTENT_TYPE);
 
        PrintWriter out = response.getWriter();
        String reset = request.getParameter(RESET_PARAM);
        String logger = request.getParameter(LOGGER_PARAM);
        String logger2 = request.getParameter(LOGGER2_PARAM);
        String level = request.getParameter(LEVEL_PARAM);
        String console = request.getParameter(CONSOLE_PARAM);
 
        if (reset != null)
        {
            if (reset.equalsIgnoreCase(TRUE))
            {
                BasicConfigurator.resetConfiguration();
                initialize();
                LOGGER.info("All logging has been reset to the default
(INFO)<br/>\n");
                out.println("All logging has been reset to the default
(INFO)<br/>\n");
            }
        }
 
        Logger pkgLogger = null;
 
        if (logger != null && logger.equals(ROOTLOGGER))
        {
            pkgLogger = Logger.getRootLogger();
        }
        else
        {
            if (logger == null || logger.equals(""))
            {
                if (logger2 == null || logger2.equals(""))
                {
                    logger = NOLOGGER;
                    LOGGER.info("No Logger specified<br/>\n");
                    out.println("No Logger specified<br/>\n");
                }
                else
                {
                    logger = logger2;
                }
            }
 
            pkgLogger = Logger.getLogger(logger);
        }
 
        if (pkgLogger != Logger.getLogger(NOLOGGER))
        {
            if (console != null)
            {
                if (console.equalsIgnoreCase(TRUE))
                {
                    /*
                    BasicConfigurator.configure(new ConsoleAppender(new
PatternLayout(
                            "<%d{MMM dd, yyyy HH:mm:ss z}> [%p] (%c)
%m%n")));
                    LOGGER.info("rootLogger has been set to output to
console<br/>\n");
                    out.println("rootLogger has been set to output to
console<br/>\n");
                    */
                    PatternLayout patternLayout = new PatternLayout(
                            "<%d{MMM dd, yyyy HH:mm:ss z}> [%p] (%c)
%m%n");
                    ConsoleAppender appender = new
ConsoleAppender(patternLayout);
                    appender.setName("console");
                    pkgLogger.addAppender(appender);
                    LOGGER.info(logger + " has been set to output to
console<br/>\n");
                    out.println(logger + " has been set to output to
console<br/>\n");
                }
                if (console.equalsIgnoreCase(FALSE))
                {
                    pkgLogger.removeAppender("console");
                    LOGGER.info(logger + " has been reset to NOT output
to console<br/>\n");
                    out.println(logger + " has been reset to NOT output
to console<br/>\n");
                }
            }
 
            if (level != null)
            {
                if (level.equalsIgnoreCase(OFF))
                {
                    pkgLogger.setLevel(Level.OFF);
                    LOGGER.info(logger + " has been set to OFF<br/>\n");
                    out.println(logger + " has been set to OFF<br/>\n");
                }
                if (level.equalsIgnoreCase(FATAL))
                {
                    pkgLogger.setLevel(Level.FATAL);
                    LOGGER.info(logger + " has been set to
FATAL<br/>\n");
                    out.println(logger + " has been set to
FATAL<br/>\n");
                }
                if (level.equalsIgnoreCase(ERROR))
                {
                    pkgLogger.setLevel(Level.ERROR);
                    LOGGER.info(logger + " has been set to
ERROR<br/>\n");
                    out.println(logger + " has been set to
ERROR<br/>\n");
                }
                if (level.equalsIgnoreCase(WARN))
                {
                    pkgLogger.setLevel(Level.WARN);
                    LOGGER.info(logger + " has been set to
WARN<br/>\n");
                    out.println(logger + " has been set to
WARN<br/>\n");
                }
                if (level.equalsIgnoreCase(INFO))
                {
                    pkgLogger.setLevel(Level.INFO);
                    LOGGER.info(logger + " has been set to
INFO<br/>\n");
                    out.println(logger + " has been set to
INFO<br/>\n");
                }
                if (level.equalsIgnoreCase(DEBUG))
                {
                    pkgLogger.setLevel(Level.DEBUG);
                    LOGGER.info(logger + " has been set to
DEBUG<br/>\n");
                    out.println(logger + " has been set to
DEBUG<br/>\n");
                }
                if (level.equalsIgnoreCase(TRACE))
                {
                    pkgLogger.setLevel(Level.TRACE);
                    LOGGER.info(logger + " has been set to
TRACE<br/>\n");
                    out.println(logger + " has been set to
TRACE<br/>\n");
                }
                if (level.equalsIgnoreCase(ALL))
                {
                    pkgLogger.setLevel(Level.ALL);
                    LOGGER.info(logger + " has been set to ALL<br/>\n");
                    out.println(logger + " has been set to ALL<br/>\n");
                }
                System.out.println("pkgLogger: " + pkgLogger + ", name:
" + 
                        pkgLogger.getName() + ", level: " +
pkgLogger.getLevel());
                pkgLogger.fatal("FATAL test for logger " + logger);
                pkgLogger.error("ERROR test for logger " + logger);
                pkgLogger.warn("WARN test for logger " + logger);
                pkgLogger.info("INFO test for logger " + logger);
                pkgLogger.debug("DEBUG test for logger " + logger);
                pkgLogger.trace("TRACE test for logger " + logger);
            }
        }
    }
 
    /**
     * doPost
     * Process the HTTP Post request.
     *
     * @param request HttpServletRequest
     * @param response HttpServletResponse
     * @throws ServletException
     * @throws IOException
     */
    public void doPost(HttpServletRequest request, HttpServletResponse
response)
            throws ServletException, IOException
    {
        doGet(request, response);
    }
 
    /**
     * doPut
     * Process the HTTP Put request.
     *
     * @param request HttpServletRequest
     * @param response HttpServletResponse
     * @throws ServletException
     * @throws IOException
     */
    public void doPut(HttpServletRequest request, HttpServletResponse
response)
            throws ServletException, IOException
    {
        doGet(request, response);
    }
 
    /**
     * destroy
     * Clean up resources.
     */
    public void destroy()
    {
        /*
        if (getRequestLogger().getAllAppenders().hasMoreElements())
        {
            getRequestLogger().removeAllAppenders();
            System.out.println("Log4J servlet destroy: " +
                    "all Log4J requestLogger appenders have been
removed.");
        }
        else
        {
            System.out.println("Log4JServlet destroy: " +
                    "No requestLogger appenders to remove.");
        }
 
        if (getRootLogger().getAllAppenders().hasMoreElements())
        {
            getRootLogger().removeAllAppenders();
            System.out.println("Log4J servlet destroy: " +
                    "all Log4J rootLogger appenders have been
removed.");
        }
        else
        {
            System.out.println("Log4JServlet destroy: " +
                    "No rootLogger appenders to remove.");
        }
 
        BasicConfigurator.resetConfiguration();
        System.out.println("Log4J servlet destroy: all Log4J appenders
have been reset.");
        */
        LogManager.shutdown();
        System.out.println("Log4J servlet destroyed: log4j has been
shutdown.");
        System.out.println("Checking for dangling rootLogger
appenders...");
        Enumeration rootAppenders = getRootLogger().getAllAppenders();
        while (rootAppenders.hasMoreElements())
        {
            Appender nextAppender =
(Appender)rootAppenders.nextElement();
            System.out.println("\tAppender: " + nextAppender + ", name="
+
                    nextAppender.getName());
            if
(FileAppender.class.isAssignableFrom(nextAppender.getClass()))
            {
                System.out.println("\t\tfilename: " +
((FileAppender)nextAppender).getFile());
            }
            if
(WriterAppender.class.isAssignableFrom(nextAppender.getClass()))
            {
                WriterAppenderQWriterAccessor viewer = 
                        new
WriterAppenderQWriterAccessor((WriterAppender)nextAppender);
                System.out.println("\t\tClosing QuietWriter: " +
viewer.getWriter());
                try
                {
                    viewer.getWriter().close();
                }
                catch (IOException ioe)
                {
                    System.out.println("\t\tException closing rootLogger
QuietWriter");
                }
                System.out.println("\t\tQuietWriter closed.");
            }
        }
        System.out.println("...done.\n");
    }
 
    /**
     * getLogger
     * Access to the underlying log4j.Logger class.
     *
     * @param clazz Class
     * @return Logger
     */
    public static Logger getLogger(Class clazz)
    {
        return Logger.getLogger(clazz);
    }
 
    /**
     * getLogger
     * Access to the underlying log4j.Logger class.
     *
     * @param loggerName String
     * @return Logger
     */
    public static Logger getLogger (String loggerName)
    {
        return Logger.getLogger(loggerName);
    }
    public static Logger getRootLogger()
    {
        return Logger.getRootLogger();
    }
 
    public static Logger getRequestLogger()
    {
        return Logger.getLogger(REQUEST_LOG);
    }
    
    public static void listAllAppenders(Logger logger)
    {
        //Check if the logger's hierarchy is the default hierarchy
        System.out.println("is the current LoggerRepository the default
LoggerRepository? ");
        System.out.println(LogManager.getLoggerRepository() == 
                logger.getLoggerRepository());
        
        System.out.println("All of the appenders: ");
        Logger rootLogger = logger.getRootLogger();
        Enumeration rootLoggerAppenders = rootLogger.getAllAppenders();
        System.out.println("rootLogger: " + rootLogger + ", name=" + 
                rootLogger.getName() + ", level =" +
rootLogger.getLevel());
        while (rootLoggerAppenders.hasMoreElements())
        {
            Appender nextAppender =
(Appender)rootLoggerAppenders.nextElement();
            System.out.println("\tAppender: " + nextAppender + ", name="
+
                    nextAppender.getName());
            if
(WriterAppender.class.isAssignableFrom(nextAppender.getClass()))
            {
                WriterAppenderQWriterAccessor viewer = 
                        new
WriterAppenderQWriterAccessor((WriterAppender)nextAppender);
                System.out.println("\t\tquietWriter: " +
viewer.getWriter());
            }
            if
(FileAppender.class.isAssignableFrom(nextAppender.getClass()))
            {
                System.out.println("\t\tfilename: " +
((FileAppender)nextAppender).getFile());
            }
        }
        Enumeration loggers =
logger.getLoggerRepository().getCurrentLoggers();
        while (loggers.hasMoreElements())
        {
            Logger nextLogger = (Logger)loggers.nextElement();
            System.out.println("Logger: " + nextLogger + ", name=" + 
                    nextLogger.getName() + ", level =" +
nextLogger.getLevel());
            Enumeration appenders = nextLogger.getAllAppenders();
            while (appenders.hasMoreElements())
            {
                Appender nextAppender =
(Appender)appenders.nextElement();
                System.out.println("\tAppender: " + nextAppender + ",
name=" +
                        nextAppender.getName());
                if
(WriterAppender.class.isAssignableFrom(nextAppender.getClass()))
                {
                    WriterAppenderQWriterAccessor viewer = 
                            new
WriterAppenderQWriterAccessor((WriterAppender)nextAppender);
                    System.out.println("\t\tquietWriter: " +
viewer.getWriter());
                }
                if
(FileAppender.class.isAssignableFrom(nextAppender.getClass()))
                {
                    System.out.println("\t\tfilename: " +
((FileAppender)nextAppender).getFile());
                }
            }
        }
    }
}
 
------------------------------------------------------------------------
---------------------------------
Extract of the the code whose log messages are getting blocked or
buffered:
 
package com.quest.nichols.genetics_rd.kaiser.controller;
.
.
.
import com.quest.nichols.genetics_rd.kaiser.calculator.*;
import com.quest.nichols.genetics_rd.kaiser.model.*;
import com.quest.nichols.genetics_rd.kaiser.validator.*;
import
com.quest.nichols.genetics_rd.kaiser.validator.InputDataBean.Race;
import com.quest.nichols.genetics_rd.kaiser.validator.InputValidator;
import com.quest.nichols.genetics_rd.log4j.CalculatorCSVPatternLayout;
import com.quest.nichols.genetics_rd.log4j.Log4JServlet;
import
com.quest.nichols.genetics_rd.utilities.PerformanceMonitoringUtilities;
 
/**
 *
 * @author rebeccah.h.prastein
 */
public class WebCalculatorController extends SimpleFormController
{
    /** Logger for this class and subclasses */
    protected final Logger LOGGER = Logger.getLogger(getClass());
    protected final Logger REQUEST_LOGGER =
Log4JServlet.getRequestLogger();
    private static DateFormat dateFormat = new
SimpleDateFormat("MM/dd/yyyy");
    
    private ReloadableResourceBundleMessageSource messageSource;
    private String callQuestView;
    private String macielErrorView;
    private ConfigData configData;
.
.
.
    @Override
    public ModelAndView  onSubmit(Object obj, BindException errors)
    {
        InputDataBean givenData = (InputDataBean)obj;
 
        //The givenData object now contains the successfully validated
data from 
        //the form, so can be written to a database, or whatever.
 
        LOGGER.info("Form data successfully submitted");
.
.
.
            if (successful)
            {
.
.
.
                successful = validator.isSuccessful(errors2);
                System.out.println("Should be logging successful Maciel
query to requestLogger");
                LOGGER.info(", should be logging successful Maciel query
to requestLogger");
                REQUEST_LOGGER.info("," +
CalculatorCSVPatternLayout.toCsvRecord(patient));
                Log4JServlet.getRequestLogger().info("Successful Maciel
query to newly re-accessed requestLogger");
 
Log4JServlet.listAllAppenders(Log4JServlet.getRootLogger());
                PerformanceMonitoringUtilities.dumpVmInfo();
            }
.
.
.
    }
.
.
.
}



------------------------------------------
The contents of this message, together with any attachments, are
intended only for the use of the person(s) to which they are
addressed and may contain confidential and/or privileged
information. Further, any medical information herein is
confidential and protected by law. It is unlawful for unauthorized
persons to use, review, copy, disclose, or disseminate confidential
medical information. If you are not the intended recipient,
immediately advise the sender and delete this message and any
attachments. Any distribution, or copying of this message, or any
attachment, is prohibited.
Mime
  • Unnamed multipart/alternative (inline, None, 0 bytes)
View raw message