Make config error messages more awesome.

Refs #5825
This commit is contained in:
Gunnar Beutner 2014-03-21 15:39:25 +01:00
parent e3ad587364
commit dde483d7a8
10 changed files with 218 additions and 40 deletions

View File

@ -90,11 +90,22 @@ static bool LoadConfigFiles(const String& appType, ValidationType validate)
int warnings = 0, errors = 0; int warnings = 0, errors = 0;
BOOST_FOREACH(const ConfigCompilerMessage& message, ConfigCompilerContext::GetInstance()->GetMessages()) { BOOST_FOREACH(const ConfigCompilerMessage& message, ConfigCompilerContext::GetInstance()->GetMessages()) {
std::ostringstream locbuf;
ShowCodeFragment(locbuf, message.Location);
String location = locbuf.str();
String logmsg;
if (!location.IsEmpty())
logmsg = "Location:\n" + location;
logmsg += String("\nConfig ") + (message.Error ? "error" : "warning") + ": " + message.Text;
if (message.Error) { if (message.Error) {
Log(LogCritical, "config", "Config error: " + message.Text); Log(LogCritical, "config", logmsg);
errors++; errors++;
} else { } else {
Log(LogWarning, "config", "Config warning: " + message.Text); Log(LogWarning, "config", logmsg);
warnings++; warnings++;
} }
} }

View File

@ -28,9 +28,9 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR})
add_library(config SHARED add_library(config SHARED
aexpression.cpp applyrule.cpp avalue.cpp base-type.conf base-type.cpp aexpression.cpp applyrule.cpp avalue.cpp base-type.conf base-type.cpp
configcompilercontext.cpp configcompiler.cpp configitembuilder.cpp configcompilercontext.cpp configcompiler.cpp configerror.cpp configitembuilder.cpp
configitem.cpp ${FLEX_config_lexer_OUTPUTS} ${BISON_config_parser_OUTPUTS} configitem.cpp ${FLEX_config_lexer_OUTPUTS} ${BISON_config_parser_OUTPUTS}
configtype.cpp expression.cpp expressionlist.cpp typerule.cpp typerulelist.cpp configtype.cpp debuginfo.cpp expression.cpp expressionlist.cpp typerule.cpp typerulelist.cpp
) )
target_link_libraries(config ${Boost_LIBRARIES} base) target_link_libraries(config ${Boost_LIBRARIES} base)

View File

@ -26,6 +26,7 @@
#include "config/configitembuilder.h" #include "config/configitembuilder.h"
#include "config/configcompiler.h" #include "config/configcompiler.h"
#include "config/configcompilercontext.h" #include "config/configcompilercontext.h"
#include "config/configerror.h"
#include "config/typerule.h" #include "config/typerule.h"
#include "config/typerulelist.h" #include "config/typerulelist.h"
#include "config/aexpression.h" #include "config/aexpression.h"
@ -173,7 +174,7 @@ void yyerror(YYLTYPE *locp, ConfigCompiler *, const char *err)
{ {
std::ostringstream message; std::ostringstream message;
message << *locp << ": " << err; message << *locp << ": " << err;
ConfigCompilerContext::GetInstance()->AddMessage(true, message.str()); ConfigCompilerContext::GetInstance()->AddMessage(true, message.str(), *locp);
} }
int yyparse(ConfigCompiler *context); int yyparse(ConfigCompiler *context);
@ -188,6 +189,9 @@ void ConfigCompiler::Compile(void)
{ {
try { try {
yyparse(this); yyparse(this);
} catch (const ConfigError& ex) {
ShowCodeFragment(std::cout, ex.GetDebugInfo());
ConfigCompilerContext::GetInstance()->AddMessage(true, DiagnosticInformation(ex));
} catch (const std::exception& ex) { } catch (const std::exception& ex) {
ConfigCompilerContext::GetInstance()->AddMessage(true, DiagnosticInformation(ex)); ConfigCompilerContext::GetInstance()->AddMessage(true, DiagnosticInformation(ex));
} }
@ -282,7 +286,7 @@ type: partial_specifier T_TYPE identifier
if (!m_Type) { if (!m_Type) {
if ($1) if ($1)
BOOST_THROW_EXCEPTION(std::invalid_argument("Partial type definition for unknown type '" + name + "'")); BOOST_THROW_EXCEPTION(ConfigError("Partial type definition for unknown type '" + name + "'", DebugInfoRange(@1, @3)));
m_Type = make_shared<ConfigType>(name, DebugInfoRange(@1, @3)); m_Type = make_shared<ConfigType>(name, DebugInfoRange(@1, @3));
m_Type->Register(); m_Type->Register();
@ -403,7 +407,7 @@ object:
free($3); free($3);
free($4); free($4);
delete $5; delete $5;
BOOST_THROW_EXCEPTION(std::invalid_argument(msgbuf.str())); BOOST_THROW_EXCEPTION(ConfigError(msgbuf.str(), di));
} }
item->SetType($3); item->SetType($3);
@ -412,7 +416,7 @@ object:
std::ostringstream msgbuf; std::ostringstream msgbuf;
msgbuf << "Name for object '" << $4 << "' of type '" << $3 << "' is invalid: Object names may not contain '!'"; msgbuf << "Name for object '" << $4 << "' of type '" << $3 << "' is invalid: Object names may not contain '!'";
free($3); free($3);
BOOST_THROW_EXCEPTION(std::invalid_argument(msgbuf.str())); BOOST_THROW_EXCEPTION(ConfigError(msgbuf.str(), @4));
} }
free($3); free($3);
@ -751,7 +755,7 @@ optional_template: /* empty */
apply: T_APPLY optional_template identifier identifier T_TO identifier T_WHERE aexpression apply: T_APPLY optional_template identifier identifier T_TO identifier T_WHERE aexpression
{ {
if (!ApplyRule::IsValidCombination($3, $6)) { if (!ApplyRule::IsValidCombination($3, $6)) {
BOOST_THROW_EXCEPTION(std::invalid_argument("'apply' cannot be used with types '" + String($3) + "' and '" + String($6) + "'.")); BOOST_THROW_EXCEPTION(ConfigError("'apply' cannot be used with types '" + String($3) + "' and '" + String($6) + "'.", @1));
} }
Array::Ptr arguments = make_shared<Array>(); Array::Ptr arguments = make_shared<Array>();

View File

@ -25,11 +25,11 @@
using namespace icinga; using namespace icinga;
void ConfigCompilerContext::AddMessage(bool error, const String& message) void ConfigCompilerContext::AddMessage(bool error, const String& message, const DebugInfo& di)
{ {
boost::mutex::scoped_lock lock(m_Mutex); boost::mutex::scoped_lock lock(m_Mutex);
m_Messages.push_back(ConfigCompilerMessage(error, message)); m_Messages.push_back(ConfigCompilerMessage(error, message, di));
} }
std::vector<ConfigCompilerMessage> ConfigCompilerContext::GetMessages(void) const std::vector<ConfigCompilerMessage> ConfigCompilerContext::GetMessages(void) const

View File

@ -31,9 +31,10 @@ struct I2_CONFIG_API ConfigCompilerMessage
{ {
bool Error; bool Error;
String Text; String Text;
DebugInfo Location;
ConfigCompilerMessage(bool error, const String& text) ConfigCompilerMessage(bool error, const String& text, const DebugInfo& di)
: Error(error), Text(text) : Error(error), Text(text), Location(di)
{ } { }
}; };
@ -43,7 +44,7 @@ struct I2_CONFIG_API ConfigCompilerMessage
class I2_CONFIG_API ConfigCompilerContext class I2_CONFIG_API ConfigCompilerContext
{ {
public: public:
void AddMessage(bool error, const String& message); void AddMessage(bool error, const String& message, const DebugInfo& di = DebugInfo());
std::vector<ConfigCompilerMessage> GetMessages(void) const; std::vector<ConfigCompilerMessage> GetMessages(void) const;
bool HasErrors(void) const; bool HasErrors(void) const;

View File

@ -0,0 +1,39 @@
/******************************************************************************
* Icinga 2 *
* Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org) *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License *
* as published by the Free Software Foundation; either version 2 *
* of the License, or (at your option) any later version. *
* *
* This program 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 General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the Free Software Foundation *
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
******************************************************************************/
#include "config/configerror.h"
using namespace icinga;
ConfigError::ConfigError(const String& message, const DebugInfo& di)
: m_Message(message), m_DebugInfo(di)
{ }
ConfigError::~ConfigError(void) throw()
{ }
const char *ConfigError::what(void) const throw()
{
return m_Message.CStr();
}
DebugInfo ConfigError::GetDebugInfo(void) const
{
return m_DebugInfo;
}

48
lib/config/configerror.h Normal file
View File

@ -0,0 +1,48 @@
/******************************************************************************
* Icinga 2 *
* Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org) *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License *
* as published by the Free Software Foundation; either version 2 *
* of the License, or (at your option) any later version. *
* *
* This program 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 General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the Free Software Foundation *
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
******************************************************************************/
#ifndef CONFIGERROR_H
#define CONFIGERROR_H
#include "config/i2-config.h"
#include "config/debuginfo.h"
namespace icinga
{
/*
* @ingroup config
*/
class I2_CONFIG_API ConfigError : public std::exception
{
public:
ConfigError(const String& message, const DebugInfo& di);
~ConfigError(void) throw();
const char *what(void) const throw();
DebugInfo GetDebugInfo(void) const;
private:
String m_Message;
DebugInfo m_DebugInfo;
};
}
#endif /* CONFIGERROR_H */

View File

@ -121,7 +121,7 @@ ExpressionList::Ptr ConfigItem::GetLinkedExpressionList(void)
std::ostringstream message; std::ostringstream message;
message << "Parent object '" << name << "' does not" message << "Parent object '" << name << "' does not"
" exist (" << m_DebugInfo << ")"; " exist (" << m_DebugInfo << ")";
ConfigCompilerContext::GetInstance()->AddMessage(true, message.str()); ConfigCompilerContext::GetInstance()->AddMessage(true, message.str(), m_DebugInfo);
} else { } else {
ExpressionList::Ptr pexprl; ExpressionList::Ptr pexprl;

97
lib/config/debuginfo.cpp Normal file
View File

@ -0,0 +1,97 @@
/******************************************************************************
* Icinga 2 *
* Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org) *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License *
* as published by the Free Software Foundation; either version 2 *
* of the License, or (at your option) any later version. *
* *
* This program 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 General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the Free Software Foundation *
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
******************************************************************************/
#include "config/debuginfo.h"
#include "base/convert.h"
#include <fstream>
using namespace icinga;
/**
* Outputs a DebugInfo struct to a stream.
*
* @param out The output stream.
* @param val The DebugInfo struct.
* @returns The output stream.
*/
std::ostream& icinga::operator<<(std::ostream& out, const DebugInfo& val)
{
out << "in " << val.Path << ": "
<< val.FirstLine << ":" << val.FirstColumn
<< "-"
<< val.LastLine << ":" << val.LastColumn;
return out;
}
DebugInfo icinga::DebugInfoRange(const DebugInfo& start, const DebugInfo& end)
{
DebugInfo result;
result.Path = start.Path;
result.FirstLine = start.FirstLine;
result.FirstColumn = start.FirstColumn;
result.LastLine = end.LastLine;
result.LastColumn = end.LastColumn;
return result;
}
void icinga::ShowCodeFragment(std::ostream& out, const DebugInfo& di)
{
if (di.Path.IsEmpty())
return;
std::ifstream ifs;
ifs.open(di.Path.CStr(), std::ifstream::in);
int lineno = 1;
char line[1024];
while (ifs.good() && lineno <= di.LastLine) {
ifs.getline(line, sizeof(line));
for (int i = 0; line[i]; i++)
if (line[i] == '\t')
line[i] = ' ';
if (lineno >= di.FirstLine && lineno <= di.LastLine) {
String pathInfo = di.Path + "(" + Convert::ToString(lineno) + "): ";
out << pathInfo;
out << line << "\n";
int start, end;
start = 0;
end = strlen(line);
if (lineno == di.FirstLine)
start = di.FirstColumn - 1;
if (lineno == di.LastLine)
end = di.LastColumn;
out << String(pathInfo.GetLength(), ' ');
out << String(start, ' ');
out << String(end - start, '^');
out << "\n";
}
lineno++;
}
}

View File

@ -59,33 +59,11 @@ struct DebugInfo
}; };
}; };
/** std::ostream& operator<<(std::ostream& out, const DebugInfo& val);
* Outputs a DebugInfo struct to a stream.
*
* @param out The output stream.
* @param val The DebugInfo struct.
* @returns The output stream.
*/
inline std::ostream& operator<<(std::ostream& out, const DebugInfo& val)
{
out << "in " << val.Path << ": "
<< val.FirstLine << ":" << val.FirstColumn
<< "-"
<< val.LastLine << ":" << val.LastColumn;
return out; DebugInfo DebugInfoRange(const DebugInfo& start, const DebugInfo& end);
}
inline DebugInfo DebugInfoRange(const DebugInfo& start, const DebugInfo& end) void ShowCodeFragment(std::ostream& out, const DebugInfo& di);
{
DebugInfo result;
result.Path = start.Path;
result.FirstLine = start.FirstLine;
result.FirstColumn = start.FirstColumn;
result.LastLine = end.LastLine;
result.LastColumn = end.LastColumn;
return result;
}
} }