/*************************************************************************** * TWAIN Data Source Manager version 2.1 * Manages image acquisition data sources used by a machine. * Copyright © 2007 TWAIN Working Group: * Adobe Systems Incorporated,AnyDoc Software Inc., Eastman Kodak Company, * Fujitsu Computer Products of America, JFL Peripheral Solutions Inc., * Ricoh Corporation, and Xerox Corporation. * All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Contact the TWAIN Working Group by emailing the Technical Subcommittee at * twainwg@twain.org or mailing us at 13090 Hwy 9, Suite 3, Boulder Creek, CA 95006. * ***************************************************************************/ /** * @file log.cpp * Log messages. * Provide logging for the messages to and from the Data Source Manager. * @author TWAIN Working Group * @date March 2007 */ #include "dsm.h" /** * Enviroment varible of path to where to write the LogFile name. * @see CTwnDsmLog */ #define kLOGENV "TWAINDSM_LOG" /** * Enviroment varible of the fopen logmode to use (if you need to * grow the log). The default behavior is to wipe it clean each time * we start up... * @see CTwnDsmLog */ #define kLOGMODEENV "TWAINDSM_LOGMODE" /** * Maximum message length we can handle... * @see CTwnDsmLog */ #define TWNDSM_MAX_MSG 1024 /** * Our implementation class where we hide our attributes... */ class CTwnDsmLogImpl { public: /// Make sure we're squeaky clean... CTwnDsmLogImpl() { memset(&pod,0,sizeof(pod)); } public: // If you add a class in future, (and I can't imagine why you // would) declare it here and not in the pod, or the memset // we do in the constructor will ruin your day... /** * We use a pod system because it help prevents us from * making dumb initialization mistakes... */ struct _pod { FILE *m_plog; /**< where we'll dump information. */ char *m_message; /**< buffer for our messages. */ char m_logpath[FILENAME_MAX]; /**< where we put the file. */ char m_logmode[16]; /**< how we fopen the file. */ int m_nIndent; /**< how far to indent the log message */ } pod; /**< Pieces of data for CTwnDsmAppsImpl*/ }; /** * The constructor for our class. This is where we see if we have a * file in the TWAINDSM_LOG environment variable. If so, then we'll * log stuff. If not, then we'll log nothing. TWAINDSM_LOGMODE * selects how we open the file. The default value is "w+", which * means it's wiped out each time a new session is started. Setting * this environmental to "a+" will cause the log information to be * appended to an existing file (a new one will still be created if * needed... */ CTwnDsmLog::CTwnDsmLog() { // Init stuff... m_ptwndsmlogimpl = new CTwnDsmLogImpl; // see if a logfile is to be used SGETENV(m_ptwndsmlogimpl->pod.m_logpath,NCHARS(m_ptwndsmlogimpl->pod.m_logpath),kLOGENV); // If we have a path, then get our mode... if (m_ptwndsmlogimpl->pod.m_logpath[0]) { SGETENV(m_ptwndsmlogimpl->pod.m_logmode,NCHARS(m_ptwndsmlogimpl->pod.m_logmode),kLOGMODEENV); if (!m_ptwndsmlogimpl->pod.m_logmode[0]) { // The default is to wipe the log clean... SSTRCPY(m_ptwndsmlogimpl->pod.m_logmode,sizeof(m_ptwndsmlogimpl->pod.m_logmode),"w"); } // Only bother to allocate a buffer if logging is on... m_ptwndsmlogimpl->pod.m_message = (char*)calloc(TWNDSM_MAX_MSG,1); if (!m_ptwndsmlogimpl->pod.m_message) { kPANIC("Unable to allocate a buffer for logging..."); } } } /** * The destructor for our class. Make sure the log is closed, * free the buffer and destroy our implementation class... */ CTwnDsmLog::~CTwnDsmLog() { if (m_ptwndsmlogimpl) { if (m_ptwndsmlogimpl->pod.m_plog) { fclose(m_ptwndsmlogimpl->pod.m_plog); } if (m_ptwndsmlogimpl->pod.m_message) { free(m_ptwndsmlogimpl->pod.m_message); } delete m_ptwndsmlogimpl; m_ptwndsmlogimpl = 0; } } /** * Logging function. * * We provide a timestamp from hours to milliseconds, which can be * used to help with performance, and to detect large, unexpected * idle times. The filename and line number in the source code is * provided. GetLastError or errno may offer a hint about a problem * with a system call, but be careful, since it's not cleared and so * it may report a message that has nothing to do with the current * calls, or anything going on in the DSM. The id of the thread that * called us is useful for finding problems with unsafe use, or use * that crosses thread boundaries in a bad way (like on Windows, when * one has to stay in the same thread as the HWND if the DAT_NULL * messages are going to work)... */ void CTwnDsmLog::Log(const int _doassert, const char* const _file, const int _line, const char* const _format, ...) { // We've nothing to do, so bail... if (0 == m_ptwndsmlogimpl->pod.m_logpath[0]) { return; } // Okay, now use the stack... UINT nError = 0; UINT nChars = 0; char *message = NULL; const char *file = NULL; // Grab the system error, this can be really useful... #if (TWNDSM_CMP == TWNDSM_CMP_VISUALCPP) nError = GetLastError(); if (nError == 0) { // Yeah, yeah...this is dumb, but I like a clean prefast log... :) nError = 0; } #elif (TWNDSM_CMP == TWNDSM_CMP_GNUGPP) nError = errno; #else #error Sorry, we do not recognize this system... #endif // If we have no log yet, try to get one... if (0 == m_ptwndsmlogimpl->pod.m_plog) { FOPEN(m_ptwndsmlogimpl->pod.m_plog,m_ptwndsmlogimpl->pod.m_logpath,m_ptwndsmlogimpl->pod.m_logmode); if (0 == m_ptwndsmlogimpl->pod.m_plog) { fprintf(stderr,"DSM: Error - logging has been disabled because logfile could not be opened: file=<%s>, mode=<%s>, errno=%d\r\n",m_ptwndsmlogimpl->pod.m_logpath,m_ptwndsmlogimpl->pod.m_logmode,errno); m_ptwndsmlogimpl->pod.m_logpath[0] = 0; } return; } // Trim the filename down to just the filename, no path... file = 0; #if (TWNDSM_CMP == TWNDSM_CMP_VISUALCPP) // Only look for this on Windows... file = strrchr(_file,'\\'); #endif if (!file) { // If we didn't find a backslash, try a forward slash... file = strrchr(_file,'/'); } if (file) { // skip the slash... file = &file[1]; } else { // Couldn't find any slashes... file = (char*)_file; } // Build the message header... #if (TWNDSM_CMP == TWNDSM_CMP_VISUALCPP) SYSTEMTIME st; GetLocalTime(&st); nChars = SNPRINTF(m_ptwndsmlogimpl->pod.m_message, TWNDSM_MAX_MSG, #if (TWNDSM_CMP_VERSION >= 1400) TWNDSM_MAX_MSG, #endif "[%02d%02d%02d%03d %-8s %4d %5u %p] %.*s", (int)st.wHour, (int)st.wMinute, (int)st.wSecond,(int)st.wMilliseconds, file, (int)_line, nError, (void*)(UINT_PTR)GETTHREADID(), m_ptwndsmlogimpl->pod.m_nIndent*2, " "); #elif (TWNDSM_CMP == TWNDSM_CMP_GNUGPP) timeval tv; tm tm; gettimeofday(&tv,NULL); tzset(); localtime_r(&tv.tv_sec,&tm); nChars = SNPRINTF(m_ptwndsmlogimpl->pod.m_message, TWNDSM_MAX_MSG, "[%02d%02d%02d%03d %-8s %4d %5d %p] %.*s", tm.tm_hour,tm.tm_min,tm.tm_sec,(int)(tv.tv_usec / 1000), file,_line, nError, (void*)GETTHREADID(), m_ptwndsmlogimpl->pod.m_nIndent*2, " "); #else #error Sorry, we do not recognize this system... #endif // This is the room remaining in the buffer, with room for a null... nChars = (TWNDSM_MAX_MSG - nChars) - 1; message = &m_ptwndsmlogimpl->pod.m_message[strlen(m_ptwndsmlogimpl->pod.m_message)]; // Finally, tack on the user portion of the message... va_list valist; va_start(valist,_format); #if (TWNDSM_CMP == TWNDSM_CMP_VISUALCPP) && (TWNDSM_CMP_VERSION >= 1400) _vsnprintf_s(message,nChars,nChars,_format,valist); #elif (TWNDSM_CMP == TWNDSM_CMP_VISUALCPP) _vsnprintf(message,nChars,_format,valist); #elif (TWNDSM_CMP == TWNDSM_CMP_GNUGPP) vsnprintf(message,nChars,_format,valist); #else #error Sorry, we do not recognize this system... #endif va_end(valist); // Write the message... fprintf(m_ptwndsmlogimpl->pod.m_plog,"%s\r\n",m_ptwndsmlogimpl->pod.m_message); fflush(m_ptwndsmlogimpl->pod.m_plog); // Do the assert, if asked for... if (_doassert) { assert(0); } } void CTwnDsmLog::Indent(int nChange) { m_ptwndsmlogimpl->pod.m_nIndent += nChange; }