diff --git a/src/Xrd/XrdConfig.cc b/src/Xrd/XrdConfig.cc index 35cf16cadb3..ec7712785bb 100644 --- a/src/Xrd/XrdConfig.cc +++ b/src/Xrd/XrdConfig.cc @@ -60,6 +60,7 @@ #include "XrdOuc/XrdOuca2x.hh" #include "XrdOuc/XrdOucEnv.hh" +#include "XrdOuc/XrdOucLogging.hh" #include "XrdOuc/XrdOucSiteName.hh" #include "XrdOuc/XrdOucStream.hh" #include "XrdOuc/XrdOucUtils.hh" @@ -215,19 +216,20 @@ int XrdConfig::Configure(int argc, char **argv) int retc, NoGo = 0, clPort = -1, optbg = 0; const char *temp; - char c, buff[512], *dfltProt, *libProt = 0, *logfn = 0; + char c, buff[512], *dfltProt, *libProt = 0; uid_t myUid = 0; gid_t myGid = 0; extern char *optarg; extern int optind, opterr; + struct XrdOucLogging::configLogInfo LogInfo; int pipeFD[2] = {-1, -1}; const char *pidFN = 0; static const int myMaxc = 80; char **urArgv, *myArgv[myMaxc], argBuff[myMaxc*3+8]; char *argbP = argBuff, *argbE = argbP+sizeof(argBuff)-4; char *ifList = 0; - int myArgc = 1, bindArg = 1, urArgc = argc, i; - bool noV6, ipV4 = false, ipV6 = false, pureLFN = false; + int myArgc = 1, urArgc = argc, i; + bool noV6, ipV4 = false, ipV6 = false; // Obtain the protocol name we will be using // @@ -303,18 +305,12 @@ int XrdConfig::Configure(int argc, char **argv) Usage(1); } break; - case 'k': if (!(bindArg = Log.logger()->ParseKeep(optarg))) + case 'k': if (!(LogInfo.keepV = Log.logger()->ParseKeep(optarg))) {Log.Emsg("Config","Invalid -k argument -",optarg); Usage(1); } break; - case 'l': if ((pureLFN = *optarg == '=')) optarg++; - if (!*optarg) - {Log.Emsg("Config", "Logfile name not specified."); - Usage(1); - } - if (logfn) free(logfn); - logfn = strdup(optarg); + case 'l': LogInfo.logArg = optarg; break; case 'L': if (!*optarg) {Log.Emsg("Config", "Protocol library path not specified."); @@ -343,7 +339,7 @@ int XrdConfig::Configure(int argc, char **argv) case 'v': cerr <setHiRes(); + case 'z': LogInfo.hiRes = true; break; default: if (optopt == '-' && *(argv[optind]+1) == '-') @@ -401,25 +397,22 @@ int XrdConfig::Configure(int argc, char **argv) if (optbg) { #ifdef WIN32 - XrdOucUtils::Undercover(&Log, !logfn); + XrdOucUtils::Undercover(&Log, !LogInfo.logArg); #else if (pipe( pipeFD ) == -1) {Log.Emsg("Config", errno, "create a pipe"); exit(17);} - XrdOucUtils::Undercover(Log, !logfn, pipeFD); + XrdOucUtils::Undercover(Log, !LogInfo.logArg, pipeFD); #endif } // Bind the log file if we have one // - if (logfn) - {char *lP; - if (!pureLFN && !(logfn = XrdOucUtils::subLogfn(Log,myInsName,logfn))) - _exit(16); + if (LogInfo.logArg) + {LogInfo.xrdEnv = &theEnv; + LogInfo.iName = myInsName; + LogInfo.cfgFn = ConfigFN; + if (!XrdOucLogging::configLog(Log, LogInfo)) _exit(16); Log.logger()->AddMsg(XrdBANNER); - if (Log.logger()->Bind(logfn, bindArg)) exit(19); - if ((lP = rindex(logfn,'/'))) {*(lP+1) = '\0'; lP = logfn;} - else lP = (char *)"./"; - XrdOucEnv::Export("XRDLOGDIR", lP); } // Get the full host name. In theory, we should always get some kind of name. @@ -545,12 +538,11 @@ int XrdConfig::Configure(int argc, char **argv) temp = (NoGo ? " initialization failed." : " initialization completed."); sprintf(buff, "%s:%d", myInstance, PortTCP); Log.Say("------ ", buff, temp); - if (logfn) + if (LogInfo.logArg) {strcat(buff, " running "); retc = strlen(buff); XrdSysUtils::FmtUname(buff+retc, sizeof(buff)-retc); Log.logger()->AddMsg(buff); - free(logfn); } return NoGo; } diff --git a/src/XrdHeaders.cmake b/src/XrdHeaders.cmake index 604a4501923..ba99e450221 100644 --- a/src/XrdHeaders.cmake +++ b/src/XrdHeaders.cmake @@ -89,6 +89,7 @@ set( XROOTD_PUBLIC_HEADERS XrdSys/XrdSysHeaders.hh XrdSys/XrdSysLinuxSemaphore.hh XrdSys/XrdSysLogger.hh + XrdSys/XrdSysLogPI.hh XrdSys/XrdSysPlatform.hh XrdSys/XrdSysPlugin.hh XrdSys/XrdSysPthread.hh diff --git a/src/XrdOuc/XrdOucLogging.cc b/src/XrdOuc/XrdOucLogging.cc new file mode 100644 index 00000000000..b10099630fb --- /dev/null +++ b/src/XrdOuc/XrdOucLogging.cc @@ -0,0 +1,292 @@ +/******************************************************************************/ +/* */ +/* X r d O u c L o g g i n g . c c */ +/* */ +/* (c) 2016 by the Board of Trustees of the Leland Stanford, Jr., University */ +/* All Rights Reserved */ +/* Produced by Andrew Hanushevsky for Stanford University under contract */ +/* DE-AC02-76-SFO0515 with the Department of Energy */ +/* */ +/* This file is part of the XRootD software suite. */ +/* */ +/* XRootD 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 3 of the License, or (at your */ +/* option) any later version. */ +/* */ +/* XRootD 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 XRootD in a file called COPYING.LESSER (LGPL license) and file */ +/* COPYING (GPL license). If not, see . */ +/* */ +/* The copyright holder's institutional names and contributor's names may not */ +/* be used to endorse or promote products derived from this software without */ +/* specific prior written permission of the institution or contributor. */ +/******************************************************************************/ + +#include +#include +#include + +#include "XrdVersion.hh" + +#include "XrdOuc/XrdOuca2x.hh" +#include "XrdOuc/XrdOucEnv.hh" +#include "XrdOuc/XrdOucLogging.hh" +#include "XrdOuc/XrdOucPinLoader.hh" +#include "XrdOuc/XrdOucStream.hh" +#include "XrdOuc/XrdOucUtils.hh" + +#include "XrdSys/XrdSysError.hh" +#include "XrdSys/XrdSysFD.hh" +#include "XrdSys/XrdSysLogging.hh" +#include "XrdSys/XrdSysLogPI.hh" +#include "XrdSys/XrdSysPthread.hh" + +/******************************************************************************/ +/* T h r e a d I n t e r f a c e s */ +/******************************************************************************/ + +namespace +{ +int cseLvl = 0; +int stdErr = 0; + +struct dbgHdr + {char theDate[6]; // yymmdd + char sep1; // single space + char theHH[2]; // hh + char colon1; // : + char theMM[2]; // mm + char colon2; // : + char theSS[2]; // SS + }; + +bool BadHdr(dbgHdr *dLine) + {if (dLine->sep1 != ' ' || dLine->colon1 != ':' || dLine->colon2 != ':') + return true; + + if (dLine->theHH[0] < '0' || dLine->theHH[0] > '2' + || dLine->theHH[1] < '0' || dLine->theHH[0] > '3' + || dLine->theMM[0] < '0' || dLine->theMM[0] > '5' + || dLine->theMM[1] < '0' || dLine->theMM[1] > '9' + || dLine->theSS[0] < '0' || dLine->theSS[0] > '5' + || dLine->theSS[1] < '0' || dLine->theSS[1] > '9') return true; + + for (int i = 0; i < 6; i++) + if (dLine->theDate[i] < '0' || dLine->theDate[i] > '9') return true; + + return false; + } + + +void *LoggingStdErr(void *carg) + {XrdOucStream seStream; + struct timeval seTime = {0,0}; + struct iovec ioV; + + seStream.Attach(stdErr, 4096); + do {if ((ioV.iov_base = seStream.GetLine())) + {ioV.iov_len = strlen((const char *)ioV.iov_base); + if (cseLvl == 1) + {if (ioV.iov_len < (int)sizeof(dbgHdr) + || BadHdr((dbgHdr *)ioV.iov_base)) continue; + } + XrdSysLogging::Forward(seTime, 0, &ioV, 1); + } + } while(true); + return 0; + } +} + +/******************************************************************************/ +/* c o n f i g L o g */ +/******************************************************************************/ + +bool XrdOucLogging:: configLog(XrdSysError &eDest, + XrdOucLogging::configLogInfo &logInfo) +{ + struct tmpstr {char *arg; char *arg2, *arg3; + tmpstr(const char *str) : arg(strdup(str)), + arg2(0), arg3(0) {} + ~tmpstr() {if (arg) free(arg); + if (arg2) free(arg2); + if (arg3) free(arg3); + } + }; + + static XrdVERSIONINFODEF(myVersion, XrdLogConfig, XrdVNUMBER, XrdVERSION); + XrdSysLogging::Parms logParms; + char *logPI = 0, *logFN = 0; + int argc; + +// Check for stderr output +// + if (!strcmp(logInfo.logArg, "-")) return true; + tmpstr opt(logInfo.logArg); + +// Check if this specified a plugin +// + if (*opt.arg == '@') + {char *parms = index(opt.arg, ','); + logPI = opt.arg+1; + if (!(*logPI)) + {eDest.Emsg("Config", "Log plugin library not specified."); + return false; + } + if (parms) + {char *eol, *pval; + int rc; + opt.arg3 = strdup(parms); *parms = 0; parms = opt.arg3; + if ((pval = varVal(",bsz=", parms, eol, ','))) + {long long bsz; + rc = XrdOuca2x::a2sz(eDest,"-l bsz",pval,&bsz,0,1048576); + if (eol) *eol = ','; + if (rc < 0) return false; + if (bsz && bsz < 8192) bsz = 8192; + logParms.bufsz = static_cast(bsz); + } + if ((pval = varVal(",cse=", parms, eol, ','))) + {rc = XrdOuca2x::a2i(eDest,"-l cse",pval,&cseLvl,0,2); + if (eol) *eol = ','; + if (rc < 0) return false; + } + logFN = varVal(",logfn=", parms, eol, ','); + } + } else logFN = opt.arg; + +// Handle any logfile name +// + if (logFN) + { if (*logFN == '=') + {if (*(logFN+1) == '\0') + {eDest.Emsg("Config", "Logfile name not specified."); + return false; + } + logParms.logfn = ++logFN; + } + else if (strcmp(logFN, "-")) + {if (!(logFN = XrdOucUtils::subLogfn(eDest,logInfo.iName,strdup(logFN)))) + return false; + logParms.logfn = opt.arg2 = logFN; + } + else logParms.logfn = logFN; + } + +// Handle plugin, if any +// + if (logPI) + {XrdSysLogPInit_t logPInit; + XrdOucPinLoader lpiLib(&eDest, &myVersion, "logging", logPI); + char **lpiArgs = configLPIArgs(logInfo.xrdEnv, argc); + if (!(logPInit = (XrdSysLogPInit_t)lpiLib.Resolve("XrdSysLogPInit"))) + {eDest.Emsg("Config","Unable to find logging plugin object in",logPI); + lpiLib.Unload(); + return false; + } + if (!(logParms.logpi = (*logPInit)(logInfo.cfgFn, lpiArgs, argc))) + {eDest.Emsg("Config", "Logging plugin initialization failed."); + lpiLib.Unload(); + return false; + } + } + +// Now complete logging configuration +// + logParms.keepV = logInfo.keepV; + logParms.hiRes = logInfo.hiRes; + if (!XrdSysLogging::Configure(*(eDest.logger()), logParms)) + {eDest.Emsg("Config", "Log configuration failed."); + return false; + } + +// Export the directory where the log file exists. We can modify the logfn +// as it should have been copied. Note logFN is the same as logParms.logfn. +// + + if (logFN && (logPI = rindex(logFN,'/'))) *(logPI+1) = '\0'; + else logParms.logfn = "./"; + XrdOucEnv::Export("XRDLOGDIR", logParms.logfn); + +// If there is a plugin but alternate output has not been specified, then we +// must capture stderr output and feed it to the logging plugin. +// + if (logPI && !logFN && cseLvl) + {pthread_t tid; + int pipeFD[2], dupStdErr = XrdSysFD_Dup(STDERR_FILENO); + if (dupStdErr < 0 || XrdSysFD_Pipe(pipeFD) < 0 + || XrdSysFD_Dup2(pipeFD[1], STDERR_FILENO) < 0) + {eDest.Emsg("Config",errno, "creating a pipe to capture stderr."); + close(dupStdErr); + return false; + } + close(pipeFD[1]); + if (XrdSysThread::Run(&tid,LoggingStdErr,(void *)0,0,"stderr router")) + {XrdSysFD_Dup2(dupStdErr, STDERR_FILENO); + eDest.Emsg("Config", errno, "start stderr router"); + close(pipeFD[0]); close(dupStdErr); return false; + } + stdErr = pipeFD[0]; + close(dupStdErr); + } + + +// All done +// + return true; +} + +/******************************************************************************/ +/* Private: c o n f i g L P I A r g s */ +/******************************************************************************/ + +char **XrdOucLogging::configLPIArgs(XrdOucEnv *envP, int &argc) +{ + static char theLPI[] = {'l', 'o', 'g', 0}; + static char *dfltArgv[] = {0, 0}; + char **lpiArgv = 0; + +// Find our arguments, if any +// + if (envP && (lpiArgv = (char **)envP->GetPtr("xrdlog.argv**"))) + argc = envP->GetInt("xrdlog.argc"); + +// Verify that we have some and substitute if not +// + if (!lpiArgv || argc < 1) + {if (!envP || !(dfltArgv[0] = (char *)envP->GetPtr("argv[0]"))) + dfltArgv[0] = theLPI; + lpiArgv = dfltArgv; + argc = 1; + } + +// Return the argv pointer +// + return lpiArgv; +} + +/******************************************************************************/ +/* Private: v a r V a l */ +/******************************************************************************/ + +char *XrdOucLogging::varVal(const char *var, char *line, char *&eol, char delim) +{ +// Find variable in the line +// + char *result = strstr(line, var); + if (!result) return 0; + +// Push up to the value and find the end +// + result += strlen(var); + if (!delim) eol = 0; + else if ((eol = index(result, delim))) *eol = 0; + +// All done +// + return result; +} diff --git a/src/XrdOuc/XrdOucLogging.hh b/src/XrdOuc/XrdOucLogging.hh new file mode 100644 index 00000000000..8f73e8c6555 --- /dev/null +++ b/src/XrdOuc/XrdOucLogging.hh @@ -0,0 +1,60 @@ +#ifndef __XRDOUCLOGGING_HH__ +#define __XRDOUCLOGGING_HH__ +/******************************************************************************/ +/* */ +/* X r d O u c L o g g i n g . h h */ +/* */ +/* (c) 2016 by the Board of Trustees of the Leland Stanford, Jr., University */ +/* All Rights Reserved */ +/* Produced by Andrew Hanushevsky for Stanford University under contract */ +/* DE-AC02-76-SFO0515 with the Department of Energy */ +/* */ +/* This file is part of the XRootD software suite. */ +/* */ +/* XRootD 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 3 of the License, or (at your */ +/* option) any later version. */ +/* */ +/* XRootD 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 XRootD in a file called COPYING.LESSER (LGPL license) and file */ +/* COPYING (GPL license). If not, see . */ +/* */ +/* The copyright holder's institutional names and contributor's names may not */ +/* be used to endorse or promote products derived from this software without */ +/* specific prior written permission of the institution or contributor. */ +/******************************************************************************/ + +class XrdSysError; +class XrdOucEnv; + +class XrdOucLogging +{ +public: + + struct configLogInfo + {const char *logArg; + XrdOucEnv *xrdEnv; + const char *iName; + const char *cfgFn; + int keepV; + bool hiRes; + configLogInfo() : logArg(0), xrdEnv(0), iName(0), cfgFn(0), + keepV(1), hiRes(false) {} + }; + +static bool configLog(XrdSysError &eDest, configLogInfo &logInfo); + + XrdOucLogging() {} + ~XrdOucLogging() {} + +private: +static char **configLPIArgs(XrdOucEnv *envP, int &argc); +static char *varVal(const char *var, char *line, char *&eol, char delim); +}; +#endif diff --git a/src/XrdSys/XrdSysLogPI.hh b/src/XrdSys/XrdSysLogPI.hh new file mode 100644 index 00000000000..d8d57d55ab0 --- /dev/null +++ b/src/XrdSys/XrdSysLogPI.hh @@ -0,0 +1,94 @@ +#ifndef __SYS_LOGPI_H__ +#define __SYS_LOGPI_H__ +/******************************************************************************/ +/* */ +/* X r d S y s L o g P I . h h */ +/* */ +/*(c) 2016 by the Board of Trustees of the Leland Stanford, Jr., University */ +/*Produced by Andrew Hanushevsky for Stanford University under contract */ +/* DE-AC02-76-SFO0515 with the Deprtment of Energy */ +/* */ +/* This file is part of the XRootD software suite. */ +/* */ +/* XRootD 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 3 of the License, or (at your */ +/* option) any later version. */ +/* */ +/* XRootD 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 XRootD in a file called COPYING.LESSER (LGPL license) and file */ +/* COPYING (GPL license). If not, see . */ +/* */ +/* The copyright holder's institutional names and contributor's names may not */ +/* be used to endorse or promote products derived from this software without */ +/* specific prior written permission of the institution or contributor. */ +/******************************************************************************/ + +#include +#include + +//----------------------------------------------------------------------------- +//! This header file defines the plugin interface used by the logging subsystem. +//! The following function is called for each message. A pointer to the +//! function is returned by XrdSysLogPInit(); see the definition below. The +//! log message function must be thread safe in synchronous mode as any number +//! of threads may call it at the same time. In async mode, only one thread +//! invokes the function for each message. +//! +//! @param mtime The time the message was generated. The time value is +//! zero when tID is zero (see below). +//! @param tID The thread ID that issued the message (Linux -> gettid()). +//! If tID is zero then the msg was captured from stderr. +//! @param msg Pointer to the null-terminaed message text. +//! @param mlen Length of the message text (exclusive of null byte). +//----------------------------------------------------------------------------- + +typedef void (*XrdSysLogPI_t)(struct timeval const &mtime, + unsigned long tID, + const char *msg, + int mlen); + +//----------------------------------------------------------------------------- +//! Initialize and return a pointer to the plugin. This function must reside in +//! the plugin shared library as an extern "C" function. The shared library is +//! library identified by the "-l @library" command line option. This function +//! is called only once during loging initialization. +//! +//! @param cfgfn -> Configuration filename (nil if none). +//! @param argv -> command line arguments after "-+xrdlog". +//! @param argc number of command line arguments in argv. +//! +//! @return Upon success a pointer of type XrdSysLogPI_t which is the function +//! that handles log messages (see above). Upon failure, a nil pointer +//! should be returned. A sample deinition is shown below. +//----------------------------------------------------------------------------- + +/*! + extern "C" XrdSysLogPI_t XrdSysLogPInit(const char *cfgn, + char **argv, + int argc) { . . . } +*/ + +typedef XrdSysLogPI_t (*XrdSysLogPInit_t)(const char *cfgn, + char **argv, + int argc); + +//------------------------------------------------------------------------------ +/*! Specify the compilation version. + + Additionally, you *should* declare the xrootd version you used to compile + your plug-in. The plugin manager automatically checks for compatability. + Declare it as follows: + + #include "XrdVersion.hh" + XrdVERSIONINFO(XrdSysLogPInit,); + + where is a 1- to 15-character unquoted name identifying your plugin. +*/ +//------------------------------------------------------------------------------ +#endif diff --git a/src/XrdSys/XrdSysLogger.cc b/src/XrdSys/XrdSysLogger.cc index 0f8d763e50c..32ab90ecff1 100644 --- a/src/XrdSys/XrdSysLogger.cc +++ b/src/XrdSys/XrdSysLogger.cc @@ -47,6 +47,7 @@ #include "XrdSys/XrdSysFD.hh" #include "XrdSys/XrdSysLogger.hh" +#include "XrdSys/XrdSysLogging.hh" #include "XrdSys/XrdSysHeaders.hh" #include "XrdSys/XrdSysPlatform.hh" #include "XrdSys/XrdSysPthread.hh" @@ -59,6 +60,8 @@ #define BLAB(x) cerr <<"Logger " <= minblen ? minblen-1 : i); } + +/******************************************************************************/ +/* Private: T i m e S t a m p */ +/******************************************************************************/ + +int XrdSysLogger::TimeStamp(struct timeval &tVal, unsigned long tID, + char *tbuff, int tbsz, bool hires) +{ + struct tm tNow; + int i; + +// Validate tbuff size +// + if (tbsz <= 0) return 0; + +// Format the time in human terms +// + localtime_r((const time_t *) &tVal.tv_sec, &tNow); + +// Choose appropriate output +// + if (hires) + {i = snprintf(tbuff, tbsz, "%02d%02d%02d %02d:%02d:%02d.%06d %03ld ", + tNow.tm_year-100, tNow.tm_mon+1, tNow.tm_mday, + tNow.tm_hour, tNow.tm_min, tNow.tm_sec, + static_cast(tVal.tv_usec), tID); + } else { + i = snprintf(tbuff, tbsz, "%02d%02d%02d %02d:%02d:%02d %03ld ", + tNow.tm_year-100, tNow.tm_mon+1, tNow.tm_mday, + tNow.tm_hour, tNow.tm_min, tNow.tm_sec, tID); + } + return (i >= tbsz ? tbsz-1 : i); +} /******************************************************************************/ /* P r i v a t e M e t h o d s */ @@ -428,16 +478,23 @@ void XrdSysLogger::FifoWait() void XrdSysLogger::putEmsg(char *msg, int msz) { - struct iovec eVec[2]; - int retc; + unsigned long tID = XrdSysThread::Num(); char tbuff[32]; + struct timeval tVal; + struct iovec eVec[2] = {{tbuff, 0}, {msg, msz}}; + int retc; + +// Get current time +// + gettimeofday(&tVal, 0); + +// Forward the message if there is a plugin involved here +// + if (doForward && XrdSysLogging::Forward(tVal, tID, &eVec[1], 1)) return; // Prefix message with time // - eVec[0].iov_base = tbuff; - eVec[0].iov_len = (int)Time(tbuff); - eVec[1].iov_base = msg; - eVec[1].iov_len = msz; + eVec[0].iov_len = TimeStamp(tVal, tID, tbuff, sizeof(tbuff), hiRes); // In theory, writev may write out a partial list. This rarely happens in // practice and so we ignore that possibility (recovery is pretty tough). diff --git a/src/XrdSys/XrdSysLogger.hh b/src/XrdSys/XrdSysLogger.hh index 97799d87b6f..7c16dbb9851 100644 --- a/src/XrdSys/XrdSysLogger.hh +++ b/src/XrdSys/XrdSysLogger.hh @@ -165,6 +165,13 @@ int ParseKeep(const char *arg); void Put(int iovcnt, struct iovec *iov); +//----------------------------------------------------------------------------- +//! Set call-out to logging plug-in on or off. +//----------------------------------------------------------------------------- + +static +void setForwarding(bool onoff) {doForward = onoff;} + //----------------------------------------------------------------------------- //! Set log file timstamp to high resolution (hh:mm:ss.uuuu). //----------------------------------------------------------------------------- @@ -226,6 +233,8 @@ private: int FifoMake(); void FifoWait(); int Time(char *tbuff); +static int TimeStamp(struct timeval &tVal, unsigned long tID, + char *tbuff, int tbsz, bool hires); struct mmMsg {mmMsg *next; @@ -248,6 +257,8 @@ bool hiRes; bool doLFR; pthread_t lfhTID; +static bool doForward; + void putEmsg(char *msg, int msz); int ReBind(int dorename=1); void Trim(); diff --git a/src/XrdSys/XrdSysLogging.cc b/src/XrdSys/XrdSysLogging.cc new file mode 100644 index 00000000000..163c61b52fa --- /dev/null +++ b/src/XrdSys/XrdSysLogging.cc @@ -0,0 +1,352 @@ +/******************************************************************************/ +/* */ +/* X r d S y s L o g g i n g . c c */ +/* */ +/*(c) 2016 by the Board of Trustees of the Leland Stanford, Jr., University */ +/*Produced by Andrew Hanushevsky for Stanford University under contract */ +/* DE-AC02-76-SFO0515 with the Deprtment of Energy */ +/* */ +/* This file is part of the XRootD software suite. */ +/* */ +/* XRootD 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 3 of the License, or (at your */ +/* option) any later version. */ +/* */ +/* XRootD 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 XRootD in a file called COPYING.LESSER (LGPL license) and file */ +/* COPYING (GPL license). If not, see . */ +/* */ +/* The copyright holder's institutional names and contributor's names may not */ +/* be used to endorse or promote products derived from this software without */ +/* specific prior written permission of the institution or contributor. */ +/******************************************************************************/ + +#include +#include +#include +#include +#include + +#include "XrdSys/XrdSysLogger.hh" +#include "XrdSys/XrdSysLogging.hh" +#include "XrdSys/XrdSysPlatform.hh" + +/******************************************************************************/ +/* S t a t i c O b j e c t s */ +/******************************************************************************/ + +namespace +{ +static const int buffOvhd = 8; + +XrdSysMutex msgMutex; +XrdSysSemaphore msgAlert(0); +XrdSysLogPI_t piLogger = 0; +char *pendMsg = 0; // msg to be processed, if nil means none +char *lastMsg = 0; // last msg in the processing queue +char *buffOrg = 0; // Base address of global message buffer +char *buffBeg = 0; // buffOrg + overhead +char *buffEnd = 0; // buffOrg + size of buffer +struct timeval todLost; // time last message was lost +int numLost = 0; // Number of messages lost +bool logDone = false; +bool doSync = false; + +static const int syncBSZ = 8192; +}; + +pthread_t XrdSysLogging::lpiTID; +bool XrdSysLogging::lclOut = false; +bool XrdSysLogging::rmtOut = false; + +/******************************************************************************/ +/* C o n f i g u r e */ +/******************************************************************************/ + +bool XrdSysLogging::Configure(XrdSysLogger &logr, Parms &parms) +{ + char eBuff[256]; + int rc; + +// Set logger parameters +// + if (parms.hiRes) logr.setHiRes(); + +// If we are going to send output to a local destination, configure it. +// + if (parms.logfn) + {if (strcmp(parms.logfn, "-") && (rc=logr.Bind(parms.logfn,parms.keepV))) + {sprintf(eBuff, "Error %d (%s) binding to log file %s.\n", + -rc, strerror(-rc), parms.logfn); + return EMsg(logr, eBuff); + } + lclOut = true; + } + +// If we are not sending output to a remote destination, we are done +// + if (!parms.logpi) {lclOut = true; return true;} + piLogger= parms.logpi; + logDone = !lclOut; + rmtOut = true; + +// We have a plugin, setup the synchronous case if so desired +// + if (!parms.bufsz) + {logr.setForwarding(true); + doSync = true; + return true; + } + +// Allocate a log buffer +// + int bsz = (parms.bufsz < 0 ? 65536 : parms.bufsz); + buffOrg = static_cast(memalign(getpagesize(), bsz)); + if (!buffOrg) return EMsg(logr, "Unable to allocate log buffer!\n"); + + buffBeg = buffOrg + buffOvhd; + buffEnd = buffOrg + bsz; + +// Start the forwarding thread +// + if (XrdSysThread::Run(&lpiTID, Send2PI, (void *)0, 0, "LogPI handler")) + {sprintf(eBuff, "Error %d (%s) starting LogPI handler.\n", + errno, strerror(errno)); + return EMsg(logr, eBuff); + } + +// We are all done +// + logr.setForwarding(true); + return true; +} + +/******************************************************************************/ +/* Private: C o p y T r u n c */ +/******************************************************************************/ + +int XrdSysLogging::CopyTrunc(char *mbuff, struct iovec *iov, int iovcnt) +{ + char *mbP = mbuff; + int segLen, bLeft = syncBSZ - 1; + +// Copy message with truncation +// + for (int i = 0; i < iovcnt; i++) + {segLen = iov[i].iov_len; + if (segLen >= bLeft) segLen = bLeft; + memcpy(mbP, iov[i].iov_base, segLen); + mbP += segLen; bLeft -= segLen; + if (bLeft <= 0) break; + } + *mbP = 0; + +// Return actual length +// + return mbP - mbuff; +} + +/******************************************************************************/ +/* Private: E M s g */ +/******************************************************************************/ + +bool XrdSysLogging::EMsg(XrdSysLogger &logr, const char *msg) +{ + struct iovec iov[] = {{0,0}, {(void *)msg,0}}; + + iov[1].iov_len = strlen((const char *)iov[1].iov_base); + logr.Put(2, iov); + return false; +} + +/******************************************************************************/ +/* F o r w a r d */ +/******************************************************************************/ + +bool XrdSysLogging::Forward(struct timeval mtime, unsigned long tID, + struct iovec *iov, int iovcnt) +{ + MsgBuff *theMsg; + char *fence, *freeMsg, *msgText; + int dwords, msgLen = 0; + bool doPost = false; + +// Calculate the message length +// + for (int i = 0; i < iovcnt; i++) msgLen += iov[i].iov_len; + +// If we are doing synchronous forwarding, do so now (we do not get a lock) +// + if (doSync) + {char *mbP, mbuff[syncBSZ]; + if (msgLen >= syncBSZ) msgLen = CopyTrunc(mbuff, iov, iovcnt); + else {mbP = mbuff; + for (int i = 0; i < iovcnt; i++) + {memcpy(mbP, iov[i].iov_base, iov[i].iov_len); + mbP += iov[i].iov_len; + } + *mbP = 0; + } + (*piLogger)(mtime, tID, mbuff, msgLen); + return logDone; + } + +// Serialize remainder of code +// + msgMutex.Lock(); + +// If the message is excessively long, treat it as a lost message +// + if (msgLen > maxMsgLen) + {todLost = mtime; + numLost++; + msgMutex.UnLock(); + return logDone; + } + +// Get the actual doublewords bytes we need (account for null byte in the msg). +// We need to increase the size by the header size if there are outsanding +// lost messages. +// + dwords = msgLen+8 + sizeof(MsgBuff); + if (numLost) dwords += sizeof(MsgBuff); + dwords = dwords/8; + +// Compute the allocation fence. The choices are as follows: +// a) When pendMsg is present then the fence is the end of the buffer if +// lastMsg >= pendMsg and pendMsg otherwise. +// b) When pendMsg is nil then we can reset the buffer pointers so that the +// fence is the end of the buffer. +// + if (pendMsg) + {freeMsg = lastMsg + ((MsgBuff *)lastMsg)->buffsz*8; + fence = (lastMsg >= pendMsg ? buffEnd : pendMsg); + } else { + freeMsg = buffBeg; + fence = buffEnd; + lastMsg = 0; + doPost = true; + } + +// Check if there is room for this message. If not, count this as a lost +// message and tell the caller full forwarding did not happen. +// + if ((freeMsg + (dwords*8)) > fence) + {todLost = mtime; + numLost++; + msgMutex.UnLock(); + return logDone; + } + +// We can allocate everything. So, check if we will be inserting a lost +// message entry here. We preallocated this above when numLost != 0; +// + if (numLost) + {theMsg = (MsgBuff *)freeMsg; + theMsg->msgtod = mtime; + theMsg->tID = tID; + theMsg->buffsz = mbDwords; + theMsg->msglen = -numLost; + if (lastMsg) ((MsgBuff *)lastMsg)->next = freeMsg - buffOrg; + lastMsg = freeMsg; + freeMsg += msgOff; + } + +// Insert the message +// + theMsg = (MsgBuff *)freeMsg; + theMsg->msgtod = mtime; + theMsg->tID = tID; + theMsg->next = 0; + theMsg->buffsz = dwords; + theMsg->msglen = msgLen; + if (lastMsg) ((MsgBuff *)lastMsg)->next = freeMsg - buffOrg; + lastMsg = freeMsg; + +// Copy the message text into the buffer +// + msgText = freeMsg + msgOff; + for (int i = 0; i < iovcnt; i++) + {memcpy(msgText, iov[i].iov_base, iov[i].iov_len); + msgText += iov[i].iov_len; + } + *msgText = 0; + +// If we need to write this to another log file do so here. +// + +// Do final post processing (release the lock prior to posting) +// + if (doPost) pendMsg = freeMsg; + msgMutex.UnLock(); + if (doPost) msgAlert.Post(); + return logDone; +} + +/******************************************************************************/ +/* Private: g e t M s g */ +/******************************************************************************/ + +XrdSysLogging::MsgBuff *XrdSysLogging::getMsg(char **msgTxt, bool cont) +{ + XrdSysMutexHelper msgHelp(msgMutex); + MsgBuff *theMsg; + +// If we got incorrectly posted, ignore this call +// + if (!pendMsg) return 0; + +// Check if this is a continuation. If so, skip to next message. If there is no +// next message, clear the pendMsg pointer to indicate we stopped pulling any +// messages (we will get posted when another message arrives). +// + if (cont) + {if (((MsgBuff *)pendMsg)->next) + pendMsg = buffOrg + ((MsgBuff *)pendMsg)->next; + else pendMsg = 0; + } + +// Return the message +// + theMsg = (MsgBuff *)pendMsg; + *msgTxt = pendMsg + msgOff; + return theMsg; +} + +/******************************************************************************/ +/* Private: S e n d 2 P I */ +/******************************************************************************/ + +void *XrdSysLogging::Send2PI(void *arg) +{ + MsgBuff *theMsg; + char *msgTxt, lstBuff[80]; + int msgLen; + bool cont; + +// Infinit loop feeding the logger plugin +// +do{msgAlert.Wait(); + cont = false; + while((theMsg = getMsg(&msgTxt, cont))) + {if ((msgLen = theMsg->msglen) < 0) + {int n = -msgLen; // Note we will never overflow lstBuff! + msgLen = snprintf(lstBuff, sizeof(lstBuff), "%d message%s lost!", + n, (n == 1 ? "" : "s")); + msgTxt = lstBuff; + } + (*piLogger)(theMsg->msgtod, theMsg->tID, msgTxt, msgLen); + cont = true; + } + } while(true); + +// Here to keep the compiler happy +// + return (void *)0; +} diff --git a/src/XrdSys/XrdSysLogging.hh b/src/XrdSys/XrdSysLogging.hh new file mode 100644 index 00000000000..e36a8a826b7 --- /dev/null +++ b/src/XrdSys/XrdSysLogging.hh @@ -0,0 +1,122 @@ +#ifndef __SYS_LOGGING_H__ +#define __SYS_LOGGING_H__ +/******************************************************************************/ +/* */ +/* X r d S y s L o g g i n g . h h */ +/* */ +/*(c) 2016 by the Board of Trustees of the Leland Stanford, Jr., University */ +/*Produced by Andrew Hanushevsky for Stanford University under contract */ +/* DE-AC02-76-SFO0515 with the Deprtment of Energy */ +/* */ +/* This file is part of the XRootD software suite. */ +/* */ +/* XRootD 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 3 of the License, or (at your */ +/* option) any later version. */ +/* */ +/* XRootD 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 XRootD in a file called COPYING.LESSER (LGPL license) and file */ +/* COPYING (GPL license). If not, see . */ +/* */ +/* The copyright holder's institutional names and contributor's names may not */ +/* be used to endorse or promote products derived from this software without */ +/* specific prior written permission of the institution or contributor. */ +/******************************************************************************/ + +#include +#include +#include +#include + +#include "XrdSys/XrdSysLogPI.hh" +#include "XrdSys/XrdSysPthread.hh" + +//----------------------------------------------------------------------------- +//! XrdSysLogging is the object that is used to route messages to a plugin +//! and is used to configure the base logger. There is only one such object. +//----------------------------------------------------------------------------- + +class XrdSysLogger; + +class XrdSysLogging +{ +public: + +//----------------------------------------------------------------------------- +//! Constructor and destructor +//! +//----------------------------------------------------------------------------- + + XrdSysLogging() {} + + ~XrdSysLogging() {} + +//----------------------------------------------------------------------------- +//! Parameters to be passed to configure. +//----------------------------------------------------------------------------- + +struct Parms + {const char *logfn; //!< -> log file name or nil if none. + XrdSysLogPI_t logpi; //!< -> log plugin object or nil if none + int bufsz; //!< size of message buffer, -1 default, or 0 + int keepV; //!< log keep argument + bool hiRes; //!< log using high resolution timestamp + Parms() : logfn(0), logpi(0), bufsz(-1), keepV(0), hiRes(false) {} + ~Parms() {} + }; + +//----------------------------------------------------------------------------- +//! Configure the logger object using the parameters above. +//! +//! @param logr Reference to the logger object. +//! @param parms Reference to the parameters. +//! +//! @return true if successful and false if log could not be configured. +//----------------------------------------------------------------------------- + +static bool Configure(XrdSysLogger &logr, Parms &parms); + +//----------------------------------------------------------------------------- +//! Forward a log message to a plugin. +//! +//! @param mtime The time the message was generated. +//! @param tID The thread ID that issued the message. +//! @param iov The vector describing what to forward. +//! @param iovcnt The number of elements in iov vector. +//! +//! @return false if the message needs to also be placed in a local log file. +//! true if all processing has completed. +//----------------------------------------------------------------------------- + +static bool Forward(struct timeval mtime, unsigned long tID, + struct iovec *iov, int iovcnt); + +private: +struct MsgBuff + {struct timeval msgtod; // time message was generated + unsigned long tID; // Thread ID issuing message + unsigned int next; // Offset to next message, 0 if none + unsigned short buffsz; // In doublewords (max is 512K-8) + short msglen; // Len of msg text (max 32K-1) if <0 ->lost msgs +// char msgtxt; // Text follows the message header + }; +static const int msgOff = sizeof(MsgBuff); +static const int mbDwords = (sizeof(MsgBuff)+7)/8*8; +static const int maxMsgLen = SHRT_MAX; + +static int CopyTrunc(char *mbuff, struct iovec *iov, int iovcnt); +static bool EMsg(XrdSysLogger &logr, const char *msg); +static MsgBuff *getMsg(char **msgTxt, bool cont); +static void *Send2PI(void *arg); + +static pthread_t lpiTID; +static bool lclOut; +static bool rmtOut; +}; +#endif diff --git a/src/XrdUtils.cmake b/src/XrdUtils.cmake index 6f4678a309e..f3dd9f46449 100644 --- a/src/XrdUtils.cmake +++ b/src/XrdUtils.cmake @@ -44,6 +44,8 @@ add_library( XrdSys/XrdSysHeaders.hh XrdSys/XrdSysError.cc XrdSys/XrdSysError.hh XrdSys/XrdSysLogger.cc XrdSys/XrdSysLogger.hh + XrdSys/XrdSysLogging.cc XrdSys/XrdSysLogging.hh + XrdSys/XrdSysLogPI.hh XrdSys/XrdSysLinuxSemaphore.hh XrdSys/XrdSysXAttr.cc XrdSys/XrdSysXAttr.hh @@ -70,6 +72,7 @@ add_library( XrdOuc/XrdOucFileInfo.cc XrdOuc/XrdOucFileInfo.hh XrdOuc/XrdOucGMap.cc XrdOuc/XrdOucGMap.hh XrdOuc/XrdOucHashVal.cc + XrdOuc/XrdOucLogging.cc XrdOuc/XrdOucLogging.hh XrdOuc/XrdOucMsubs.cc XrdOuc/XrdOucMsubs.hh XrdOuc/XrdOucName2Name.cc XrdOuc/XrdOucName2Name.hh XrdOuc/XrdOucN2NLoader.cc XrdOuc/XrdOucN2NLoader.hh diff --git a/src/XrdVersionPlugin.hh b/src/XrdVersionPlugin.hh index f2702f4e5d7..9227ea094d0 100644 --- a/src/XrdVersionPlugin.hh +++ b/src/XrdVersionPlugin.hh @@ -96,6 +96,7 @@ XrdVERSIONPLUGIN_Rule(DoNotChk, 4, 0, XrdgetProtocol )\ XrdVERSIONPLUGIN_Rule(Required, 4, 0, XrdgetProtocolPort )\ XrdVERSIONPLUGIN_Rule(Optional, 4, 0, XrdHttpGetSecXtractor )\ + XrdVERSIONPLUGIN_Rule(Required, 4, 0, XrdSysLogPInit )\ XrdVERSIONPLUGIN_Rule(Required, 4, 0, XrdOssGetStorageSystem )\ XrdVERSIONPLUGIN_Rule(Required, 4, 0, XrdOssStatInfoInit )\ XrdVERSIONPLUGIN_Rule(Required, 4, 0, XrdOucGetCache )\