Added %err_code and %err_detail logformat codes to record details about transaction failures
For example, when Squid responds with 500 Internal Server Error, it is often
useful to know what went wrong.
This patch :
- log a detail string instead of numbers for common errors
- log a label with the detail code number for generic errors (system errors,
or exceptions)
- adds more details about errors, especially those detected with exceptions:
We record a hash of the filename and the source code line number of the
first caught exception.
- adds two scripts which can help the developers to find the exact position
of the caught exception:
1) The calc-must-ids.pl take as argument one or more files and compute for
each Must expression in the given files its id;
2) The calc-must-ids.sh can be used to find the exact position of a Must
expression from its id. Example usage:
# ./scripts/calc-must-ids.sh 0xB79EF14
./src/adaptation/ecap/MessageRep.cc:356: 0xB79EF14 Must(false);
--- /dev/null
+#!/usr/bin/perl
+#
+# Author: Tsantilas Christos
+# (C) 2010 The Measurement Factory
+#
+# Usage:
+# calc-must-ids.pl file1 file2 ...
+# Compute the ids of Must expressions of the given files.
+# It returns one line per Must expression in the form:
+# filename: line: MustID 'Must Text'
+#
+
+use warnings;
+use strict;
+
+# This constant should be synced with ERR_DETAIL_EXCEPTION_START enum
+# defined in src/err_detail_type.h
+use constant ERR_DETAIL_EXCEPTION_START => 110000;
+
+my $file;
+while ($file = shift @ARGV) {
+ ComputeMustIds($file);
+}
+sub FileNameHash
+{
+# Please keep in sync this function with the FileNameHash function in
+# src/base/TextException.cc file
+ my($name) = @_;
+ $name =~ s/.*\///g;
+ my($i) = 0;
+ my($j) =0;
+ my($n) = 0;
+ my(@na) = split(//, $name);
+ for($j=0; $j < @na; $j++) {
+ $n = $n ^ (271 * ord($na[$j]));
+ }
+ $i = $n ^ ($j *271);
+
+ # Currently 18bits of a 32 bit integer used for filename hash
+ # (max hash=262143), and 14 bits for storing line number
+ $i = $i % 262143;
+ return $i;
+}
+
+sub ComputeMustIds
+{
+ my($file) = @_;
+ my($hash) = FileNameHash($file);
+ if(!open(IN, "<$file")) {
+ printf STDERR "error opening file $file. Ignore ...";
+ return;
+ }
+ while(<IN>) {
+ my($line) = $_;
+ my($id);
+ if ( $line =~ /^\s*Must\s*\(/ || $line =~ /^\s*throw\s*TexcHere\s*\(/){
+ $line =~ s/^\s*//;
+ $id= ($hash <<14) | ($. & 0x3FFF);
+ $id += ERR_DETAIL_EXCEPTION_START;
+# print "$file:$.: $id $line";
+ printf "%s:%d: 0x%X %s", $file, $., $id, $line;
+ }
+ }
+ close(IN);
+}
--- /dev/null
+#!/bin/sh
+#
+# Usage:
+# calc-must-ids.sh [MustID]
+# Given an id it searches for the related Must expression in all
+# source files. If no arguments given it returns all Must expressions
+# with its ids and their exact position in the source files.
+#
+# Example usage:
+# # ./scripts/calc-must-ids.sh 0xB79EF14
+# ./src/adaptation/ecap/MessageRep.cc:356: 0xB79EF14 Must(false);
+#
+
+if test -z "$1"; then
+ find . -name "*.cc" -o -name "*.h" -o -name "*.cci" | \
+ xargs `dirname $0`/calc-must-ids.pl
+else
+ find . -name "*.cc" -o -name "*.h" -o -name "*.cci" | \
+ xargs `dirname $0`/calc-must-ids.pl | grep ": $1 "
+fi
+
+
#include "adaptation/icap/icap_log.h"
#endif
#include "acl/FilledChecklist.h"
+#include "err_detail_type.h"
HttpRequest::HttpRequest() : HttpMsg(hoRequest)
{
// hier
dnsWait = -1;
errType = ERR_NONE;
+ errDetail = ERR_DETAIL_NONE;
peer_login = NULL; // not allocated/deallocated by this class
peer_domain = NULL; // not allocated/deallocated by this class
vary_headers = NULL;
return body_pipe != NULL && body_pipe->consumedSize() > 0;
}
+void
+HttpRequest::detailError(err_type aType, int aDetail)
+{
+ if (errType || errDetail)
+ debugs(11, 5, HERE << "old error details: " << errType << '/' << errDetail);
+ debugs(11, 5, HERE << "current error details: " << aType << '/' << aDetail);
+ // checking type and detail separately may cause inconsistency, but
+ // may result in more details available if they only become available later
+ if (!errType)
+ errType = aType;
+ if (!errDetail)
+ errDetail = aDetail;
+}
+
const char *HttpRequest::packableURI(bool full_uri) const
{
if (full_uri)
// may eventually need cloneNullAdaptationImmune() for that.
flags = aReq->flags.cloneAdaptationImmune();
+ errType = aReq->errType;
+ errDetail = aReq->errDetail;
+
auth_user_request = aReq->auth_user_request;
if (aReq->pinned_connection) {
void recordLookup(const DnsLookupDetails &detail);
+ /// sets error detail if no earlier detail was available
+ void detailError(err_type aType, int aDetail);
+
protected:
void clean();
int dnsWait; ///< sum of DNS lookup delays in milliseconds, for %dt
err_type errType;
+ int errDetail; ///< errType-specific detail about the transaction error
char *peer_login; /* Configured peer login:password */
cf_gen_defines.cci \
cf_parser.cci \
err_type.cc \
+ err_detail_type.cc \
globals.cc \
hier_code.cc \
icp_opcode.cc \
err_type.cc: err_type.h mk-string-arrays.awk
$(AWK) -f $(srcdir)/mk-string-arrays.awk < $(srcdir)/err_type.h > $@ || ($(RM) -f $@ && exit 1)
+err_detail_type.cc: err_detail_type.h mk-string-arrays.awk
+ $(AWK) -f $(srcdir)/mk-string-arrays.awk < $(srcdir)/err_detail_type.h | sed 's/ERR_DETAIL_//' > $@ || ($(RM) -f $@ && exit 1)
+
lookup_t.cc: lookup_t.h mk-string-arrays.awk
$(AWK) -f $(srcdir)/mk-string-arrays.awk < $(srcdir)/lookup_t.h > $@ || ($(RM) -f $@ && exit 1)
#include "HttpRequest.h"
#include "HttpReply.h"
#include "errorpage.h"
+#include "err_detail_type.h"
#include "SquidTime.h"
#if USE_ADAPTATION
if (entry->isEmpty()) {
debugs(11,9, HERE << "creating ICAP error entry after ICAP failure");
ErrorState *err = errorCon(ERR_ICAP_FAILURE, HTTP_INTERNAL_SERVER_ERROR, request);
- err->xerrno = errno;
+ err->xerrno = ERR_DETAIL_ICAP_RESPMOD_EARLY;
fwd->fail(err);
fwd->dontRetry(true);
}
+ else if (request) { // update logged info directly
+ request->detailError(ERR_ICAP_FAILURE, ERR_DETAIL_ICAP_RESPMOD_LATE);
+ }
abortTransaction("ICAP failure");
}
#include "HttpRequest.h"
#include "HttpReply.h"
#include "SquidTime.h"
+#include "err_detail_type.h"
// flow and terminology:
// HTTP| --> receive --> encode --> write --> |network
void Adaptation::Icap::ModXact::callException(const std::exception &e)
{
if (!canStartBypass || isRetriable) {
+ if (!isRetriable) {
+ if (const TextException *te = dynamic_cast<const TextException *>(&e))
+ detailError(ERR_DETAIL_EXCEPTION_START + te->id());
+ else
+ detailError(ERR_DETAIL_EXCEPTION_OTHER);
+ }
Adaptation::Icap::Xaction::callException(e);
return;
}
debugs(93, 3, HERE << "bypassing " << inCall << " exception: " <<
e.what() << ' ' << status());
bypassFailure();
+ } catch (const TextException &bypassTe) {
+ detailError(ERR_DETAIL_EXCEPTION_START + bypassTe.id());
+ Adaptation::Icap::Xaction::callException(bypassTe);
} catch (const std::exception &bypassE) {
+ detailError(ERR_DETAIL_EXCEPTION_OTHER);
Adaptation::Icap::Xaction::callException(bypassE);
}
}
// adapted body consumer aborted
void Adaptation::Icap::ModXact::noteBodyConsumerAborted(BodyPipe::Pointer)
{
+ detailError(ERR_DETAIL_ICAP_XACT_BODY_CONSUMER_ABORT);
mustStop("adapted body consumer aborted");
}
stopWriting(false);
stopSending(false);
+ if (theInitiator.set()) // we have not sent the answer to the initiator
+ detailError(ERR_DETAIL_ICAP_XACT_OTHER);
+
// update adaptation history if start was called and we reserved a slot
Adaptation::History::Pointer ah = virginRequest().adaptLogHistory();
if (ah != NULL && adaptHistoryId >= 0)
return true;
}
+void Adaptation::Icap::ModXact::detailError(int errDetail)
+{
+ if (HttpRequest *request = virgin.cause ?
+ virgin.cause : dynamic_cast<HttpRequest*>(virgin.header)) {
+ request->detailError(ERR_ICAP_FAILURE, errDetail);
+ }
+}
/* Adaptation::Icap::ModXactLauncher */
// bypasses exceptions if needed and possible
virtual void callException(const std::exception &e);
+ /// record error detail in the virgin request if possible
+ virtual void detailError(int errDetail);
+
private:
virtual void start();
#include "icap_log.h"
#include "fde.h"
#include "SquidTime.h"
+#include "err_detail_type.h"
static PconnPool *icapPconnPool = new PconnPool("ICAP Servers");
{
debugs(93, 2, HERE << typeName <<
" failed to connect to " << service().cfg().uri);
+ detailError(ERR_DETAIL_ICAP_XACT_START);
throw TexcHere("cannot connect to the ICAP service");
}
void Adaptation::Icap::Xaction::handleCommClosed()
{
+ detailError(ERR_DETAIL_ICAP_XACT_CLOSE);
mustStop("ICAP service connection externally closed");
}
if (theInitiator.set()) {
clearInitiator();
+ detailError(ERR_DETAIL_ICAP_INIT_GONE);
mustStop("initiator aborted");
}
virtual void handleCommTimedout();
virtual void handleCommClosed();
+ /// record error detail if possible
+ virtual void detailError(int errDetail) {}
+
void openConnection();
void closeConnection();
void dieOnConnectionFailure();
-#include "config.h"
+#include "squid.h"
#include "base/TextException.h"
#include "Debug.h"
#include "util.h"
message=NULL;
theFileName=NULL;
theLineNo=0;
+ theId=0;
}
TextException::TextException(const TextException& right) :
- message((right.message?xstrdup(right.message):NULL)), theFileName(right.theFileName), theLineNo(right.theLineNo)
+ message((right.message?xstrdup(right.message):NULL)), theFileName(right.theFileName), theLineNo(right.theLineNo), theId(right.theId)
{
}
-TextException::TextException(const char *aMsg, const char *aFileName, int aLineNo):
- message(xstrdup(aMsg)), theFileName(aFileName), theLineNo(aLineNo)
+TextException::TextException(const char *aMsg, const char *aFileName, int aLineNo, unsigned int anId):
+ message(xstrdup(aMsg)), theFileName(aFileName), theLineNo(aLineNo), theId(anId)
{}
TextException::~TextException() throw()
message=(right.message?xstrdup(right.message):NULL);
theFileName=right.theFileName;
theLineNo=right.theLineNo;
-
+ theId=right.theId;
return *this;
}
return message ? message : "TextException without a message";
}
-void Throw(const char *message, const char *fileName, int lineNo)
+unsigned int TextException::FileNameHash(const char *fname)
+{
+ const char *s = NULL;
+ unsigned int n = 0;
+ unsigned int j = 0;
+ unsigned int i = 0;
+ s = strrchr(fname, '/');
+
+ if (s)
+ s++;
+ else
+ s = fname;
+
+ while (*s) {
+ j++;
+ n ^= 271 * (unsigned) *s++;
+ }
+ i = n ^ (j * 271);
+ /*18bits of a 32 bit integer used for filename hash (max hash=262143),
+ and 14 bits for storing line number (16k max).
+ If you change this policy remember to update the FileNameHash function
+ in the scripts/calc-must-ids.pl script
+ */
+ return i % 262143;
+}
+
+void Throw(const char *message, const char *fileName, int lineNo, unsigned int id)
{
// or should we let the exception recepient print the exception instead?
(message ? ": " : ".") << (message ? message : ""));
}
- throw TextException(message, fileName, lineNo);
+ throw TextException(message, fileName, lineNo, id);
}
#include "config.h"
#include <exception>
+static unsigned int FileNameHashCached(const char *fname);
+
// simple exception to report custom errors
// we may want to change the interface to be able to report system errors
public:
TextException();
- TextException(const char *aMessage, const char *aFileName = 0, int aLineNo = -1);
+ TextException(const char *aMessage, const char *aFileName = 0, int aLineNo = -1, unsigned int anId =0);
TextException(const TextException& right);
virtual ~TextException() throw();
+ // unique exception ID for transaction error detail logging
+ unsigned int id() const { return theId; }
+
virtual const char *what() const throw();
TextException& operator=(const TextException &right);
char *message; // read-only
protected:
+ /// a small integer hash value to semi-uniquely identify the source file
+ static unsigned int FileNameHash(const char *fname);
+
// optional location information
const char *theFileName;
int theLineNo;
+ unsigned int theId;
+
+ friend unsigned int FileNameHashCached(const char *fname);
};
//inline
// return exx.print(os);
//}
+/// caches the result of FileNameHash() for each translation unit
+static unsigned int
+FileNameHashCached(const char *fname)
+{
+ static const char *lastFname = 0;
+ static int lastHash = 0;
+ // __FILE__ changes when we #include files
+ if (lastFname != fname) { // cheap pointer comparison
+ lastFname = fname;
+ lastHash = TextException::FileNameHash(fname);
+ }
+ return lastHash;
+}
+
+/// Avoids "defined but not used" warnings for FileNameHashCached
+class FileNameHashCacheUser {
+ bool use(void *ptr=NULL) { return ptr != &FileNameHashCached;}
+};
+
#if !defined(TexcHere)
-# define TexcHere(msg) TextException((msg), __FILE__, __LINE__)
+# define TexcHere(msg) TextException((msg), __FILE__, __LINE__, \
+ (FileNameHashCached(__FILE__)<<14) | (__LINE__ & 0x3FFF))
#endif
-extern void Throw(const char *message, const char *fileName, int lineNo);
+extern void Throw(const char *message, const char *fileName, int lineNo, unsigned int id);
// Must(condition) is like assert(condition) but throws an exception instead
#if !defined(Must)
# define Must(cond) ((cond) ? \
(void)0 : \
- (void)Throw(#cond, __FILE__, __LINE__))
+ (void)Throw(#cond, __FILE__, __LINE__, \
+ (FileNameHashCached(__FILE__)<<14) | (__LINE__ & 0x3FFF)))
#endif
#endif /* SQUID__TEXTEXCEPTION_H */
default %d/%b/%Y:%H:%M:%S %z
tr Response time (milliseconds)
dt Total time spent making DNS lookups (milliseconds)
+ err_code The ID of an error response served by Squid or
+ a similar internal error identifier.
+ err_detail Additional err_code-dependent error information.
HTTP cache related format codes:
// WTF?? request->auth_user_request = NULL;
}
+
+ if (aLogEntry->request) {
+ aLogEntry->request->errType = request->errType;
+ aLogEntry->request->errDetail = request->errDetail;
+ }
}
void
#include "Store.h"
#include "SquidTime.h"
#include "wordlist.h"
+#include "err_detail_type.h"
#if LINGERING_CLOSE
{
clearAdaptation(virginHeadSource);
assert(!adaptedBodySource);
- handleAdaptationFailure(!final);
+ handleAdaptationFailure(ERR_DETAIL_ICAP_REQMOD_ABORT, !final);
}
void
{
assert(!virginHeadSource);
stopConsumingFrom(adaptedBodySource);
- handleAdaptationFailure();
+ handleAdaptationFailure(ERR_DETAIL_ICAP_RESPMOD_CLT_SIDE_BODY);
}
void
-ClientHttpRequest::handleAdaptationFailure(bool bypassable)
+ClientHttpRequest::handleAdaptationFailure(int errDetail, bool bypassable)
{
debugs(85,3, HERE << "handleAdaptationFailure(" << bypassable << ")");
(c != NULL && c->auth_user_request != NULL ?
c->auth_user_request : request->auth_user_request));
+ request->detailError(ERR_ICAP_FAILURE, errDetail);
+
node = (clientStreamNode *)client_stream.tail->data;
clientStreamRead(node, this, node->readBuffer);
}
void startAdaptation(const Adaptation::ServiceGroupPointer &g);
// private but exposed for ClientRequestContext
- void handleAdaptationFailure(bool bypassable = false);
+ void handleAdaptationFailure(int errDetail, bool bypassable = false);
private:
// Adaptation::Initiator API
--- /dev/null
+#ifndef _SQUID_ERR_DETAIL_H
+#define _SQUID_ERR_DETAIL_H
+
+typedef enum {
+ ERR_DETAIL_NONE,
+ ERR_DETAIL_START = 100000, // to avoid clashes with most OS error numbers
+ ERR_DETAIL_ICAP_REQMOD_ABORT = ERR_DETAIL_START, // transaction abort
+ ERR_DETAIL_ICAP_RESPMOD_CLT_SIDE_BODY, // client-side detected body abort
+ ERR_DETAIL_ICAP_RESPMOD_EARLY, // RESPMOD failed w/o store entry
+ ERR_DETAIL_ICAP_RESPMOD_LATE, // RESPMOD failed with a store entry
+ ERR_DETAIL_ICAP_XACT_START, // transaction start failure
+ ERR_DETAIL_ICAP_XACT_BODY_CONSUMER_ABORT, // transaction body consumer gone
+ ERR_DETAIL_ICAP_INIT_GONE, // initiator gone
+ ERR_DETAIL_ICAP_XACT_CLOSE, // ICAP connection closed unexpectedly
+ ERR_DETAIL_ICAP_XACT_OTHER, // other ICAP transaction errors
+ ERR_DETAIL_EXCEPTION_OTHER, //other errors ( eg std C++ lib errors)
+ ERR_DETAIL_MAX,
+ ERR_DETAIL_EXCEPTION_START = 110000 // offset for exception ID details
+} err_detail_type;
+
+extern const char *err_detail_type_str[];
+
+inline
+const char *errorDetailName(int errDetailId) {
+ if (errDetailId < ERR_DETAIL_START)
+ return "SYSERR";
+
+ if (errDetailId < ERR_DETAIL_MAX)
+ return err_detail_type_str[errDetailId-ERR_DETAIL_START+2];
+
+ if (errDetailId >=ERR_DETAIL_EXCEPTION_START)
+ return "EXCEPTION";
+
+ return "UNKNOWN";
+}
+
+#endif
#include "rfc1738.h"
#include "URLScheme.h"
#include "wordlist.h"
+#include "err_detail_type.h"
/**
\defgroup ErrorPageInternal Error Page Internals
}
/// \ingroup ErrorPageInternal
-static const char *
+const char *
errorPageName(int pageId)
{
if (pageId >= ERR_NONE && pageId < ERR_MAX) /* common case */
if (request != NULL) {
err->request = HTTPMSGLOCK(request);
err->src_addr = request->client_addr;
+ request->detailError(type, ERR_DETAIL_NONE);
}
return err;
*/
if (err->request)
- err->request->errType = err->type;
+ err->request->detailError(err->type, err->xerrno);
/* moved in front of errorBuildBuf @?@ */
err->flags.flag_cbdata = 1;
* This function creates a ErrorState object.
*/
SQUIDCEXTERN ErrorState *errorCon(err_type type, http_status, HttpRequest * request);
+SQUIDCEXTERN const char *errorPageName(int pageId); ///< error ID to string
#endif /* SQUID_ERRORPAGE_H */
if (!errorState->request)
errorState->request = HTTPMSGLOCK(request);
+
+ request->detailError(errorState->type, errorState->xerrno);
}
/**
http_code = HTTP_INTERNAL_SERVER_ERROR;
}
+ if (ftpState->request)
+ ftpState->request->detailError(err_code, code);
+
err = errorCon(err_code, http_code, ftpState->request);
if (ftpState->old_request)
HttpStateData::handleRequestBodyProducerAborted()
{
ServerStateData::handleRequestBodyProducerAborted();
+ if (entry->isEmpty()) {
+ debugs(11, 3, "request body aborted: FD " << fd);
+ ErrorState *err;
+ err = errorCon(ERR_READ_ERROR, HTTP_BAD_GATEWAY, fwd->request);
+ err->xerrno = errno;
+ fwd->fail(err);
+ }
+
abortTransaction("request body producer aborted");
}
// Store.h Required by configuration directives parsing/dumping only
#include "Store.h"
+#include "errorpage.h"
+#include "err_detail_type.h"
#include "acl/Checklist.h"
#include "CacheManager.h"
#if USE_SQUID_EUI
LFT_HTTP_BODY_BYTES_READ,
LFT_SQUID_STATUS,
- /*LFT_SQUID_ERROR, */
+ LFT_SQUID_ERROR,
+ LFT_SQUID_ERROR_DETAIL,
LFT_SQUID_HIERARCHY,
LFT_MIME_TYPE,
{"<bs", LFT_HTTP_BODY_BYTES_READ},
{"Ss", LFT_SQUID_STATUS},
- /*{ "Se", LFT_SQUID_ERROR }, */
+ { "err_code", LFT_SQUID_ERROR },
+ { "err_detail", LFT_SQUID_ERROR_DETAIL },
{"Sh", LFT_SQUID_HIERARCHY},
{"mt", LFT_MIME_TYPE},
break;
- /* case LFT_SQUID_ERROR: */
+ case LFT_SQUID_ERROR:
+ if (al->request && al->request->errType != ERR_NONE)
+ out = errorPageName(al->request->errType);
+ break;
+
+ case LFT_SQUID_ERROR_DETAIL:
+ if (al->request && al->request->errDetail != ERR_DETAIL_NONE) {
+ if (al->request->errDetail > ERR_DETAIL_START &&
+ al->request->errDetail < ERR_DETAIL_MAX)
+ out = errorDetailName(al->request->errDetail);
+ else {
+ if (al->request->errDetail >= ERR_DETAIL_EXCEPTION_START)
+ snprintf(tmp, sizeof(tmp), "%s=0x%X",
+ errorDetailName(al->request->errDetail), (uint32_t) al->request->errDetail);
+ else
+ snprintf(tmp, sizeof(tmp), "%s=%d",
+ errorDetailName(al->request->errDetail), al->request->errDetail);
+ out = tmp;
+ }
+ }
+ break;
case LFT_SQUID_HIERARCHY:
if (al->hier.ping.timedout)