这是本节的多页打印视图。 点击此处打印.

返回本页常规视图.

错误、警告和日志记录

SDK 为 UDx 提供了多种报告错误、警告和其他消息的方法。对于使用 C++ 或 Python 编写的 UDx,请使用发送消息中描述的消息传递 API。如处理错误中所述,所有语言的 UDx 都可能因出错而停止执行。

此外,UDx 还可以将消息写入 Vertica 日志,而使用 C++ 编写的 UDx 可以将消息写入系统表。

1 - 发送消息

UDx 可以通过报告错误并终止执行来处理问题,但在某些情况下,您可能希望发送警告并继续。例如,UDx 可能会忽略或使用意外输入的默认值,并报告它已经这样做了。C++ 和 Python 消息传递 API 支持报告不同严重性级别的消息。

Udx 对 ServerInterface 实例具有访问权限。此类具有以下按严重性顺序报告消息的方法:

  • reportError (也会终止执行)

  • reportWarning

  • reportNotice

  • reportInfo

每种方法都会生成包含以下组件的消息:

  • ID 代码:标识码,为任意整数。此代码不会与 Vertica 错误代码交互。

  • 消息字符串:对问题的简要描述。

  • 可选详细信息字符串:提供更多上下文信息。

  • 可选提示字符串:提供其他指导。

如果重复的消息具有相同的代码和消息字符串,则即使详细信息和提示字符串不同,它们也会被压缩到一个报告中。

构造消息

UDx 通常应在进程调用期间立即报告错误。对于所有其他消息类型,请在处理期间记录信息并从 UDx 的 destroy 方法调用报告方法。如果在处理期间调用其他报告方法,则不会生成输出。

构造消息的过程特定于语言。

C++

每个 ServerInterface 报告方法均使用 ClientMessage 实参。ClientMessage 类具有以下用于设置代码和消息、详细信息及提示的方法:

  • makeMessage: 设置 ID 代码和消息字符串。

  • setDetail: 设置可选的详细信息字符串。

  • setHint: 设置可选的提示字符串。

这些方法调用可以链接起来以简化消息的创建和传递。

所有字符串都支持 printf 样式的实参和格式。

在以下示例中,函数在 processBlock 中记录问题并在 destroy 中报告它们:


class PositiveIdentity : public Vertica::ScalarFunction
    {
public:
    using ScalarFunction::destroy;
    bool hitNotice = false;

    virtual void processBlock(Vertica::ServerInterface &srvInterface,
                              Vertica::BlockReader &arg_reader,
                              Vertica::BlockWriter &res_writer)
    {
        do {
            const Vertica::vint a = arg_reader.getIntRef(0);
            if (a < 0 && a != vint_null) {
                hitNotice = true;
                res_writer.setInt(null);
            } else {
                res_writer.setInt(a);
            }
            res_writer.next();
        } while (arg_reader.next());
    }

    virtual void destroy(ServerInterface &srvInterface,
                         const SizedColumnTypes &argTypes) override
    {
        if (hitNotice) {
            ClientMessage msg = ClientMessage::makeMessage(100, "Passed negative argument")
                                .setDetail("Value set to null");
            srvInterface.reportNotice(msg);
        }
    }
}

Python

每个 ServerInterface 报告方法都包含以下位置实参和关键字实参:

  • idCode:整数 ID 代码,为位置实参。

  • message:消息文本,为位置实参。

  • hint:可选提示文本,为关键字实参。

  • detail:可选的详细信息文本,为关键字实参。

所有实参都支持 str.format()f-string 格式。

在以下示例中,函数在 processBlock 中记录问题并在 destroy 中报告它们:


class PositiveIdentity(vertica_sdk.ScalarFunction):
    def __init__(self):
        self.hitNotice = False

    def processBlock(self, server_interface, arg_reader, res_writer):
        while True:
            arg = arg_reader.getInt(0)
            if arg < 0 and arg is not None:
                self.hitNotice = True
                res_writer.setNull()
            else:
                res_writer.setInt(arg)
            res_writer.next()
            if not arg_reader.next():
                break

    def destroy(self, srv, argType):
        if self.hitNotice:
            srv.reportNotice(100, "Passed negative arguement", detail="Value set to null")
        return

API

在调用 ServerInterface 报告方法之前,请使用 ClientMessage 类构造并填充消息。

ServerInterface API 会提供以下报告消息的方法:


// ClientMessage methods
template<typename... Argtypes>
static ClientMessage makeMessage(int errorcode, const char *fmt, Argtypes&&... args);

template <typename... Argtypes>
ClientMessage & setDetail(const char *fmt, Argtypes&&... args);

template <typename... Argtypes>
ClientMessage & setHint(const char *fmt, Argtypes&&... args);

// ServerInterface reporting methods
virtual void reportError(ClientMessage msg);

virtual void reportInfo(ClientMessage msg);

virtual void reportNotice(ClientMessage msg);

virtual void reportWarning(ClientMessage msg);

ServerInterface API 会提供以下报告消息的方法:


def reportError(self, code, text, hint='', detail=''):

def reportInfo(self, code, text, hint='', detail=''):

def reportNotice(self, code, text, hint='', detail=''):

def reportWarning(self, code, text, hint='', detail=''):

2 - 处理错误

如果 UDx 遇到无法恢复的错误,它应报告错误并终止。如何做到这一点取决于编写时所用的语言:

  • C++:考虑使用发送消息中所述的 API,它比本主题中描述的错误处理更具表现力。或者,您可以使用 vt_report_error 宏来报告错误并退出。该宏使用以下两个参数:错误号和错误消息字符串。错误号和消息都会显示在 Vertica 向用户报告的错误中。错误号并不由 Vertica 定义。您可以随意使用任何值。

  • Java:实例化并引发 UdfException,它会将数字代码和消息字符串报告给用户。

  • Python:考虑使用发送消息中所述的 API,它比本主题中描述的错误处理更具表现力。或者,引发内置到 Python 语言中的异常;SDK 不包含特定于 UDx 的异常。

  • R:使用 stop 来停止执行并显示一条消息。

异常或停止会导致包含函数调用的事务被回滚。

以下示例演示了错误处理:

以下函数会将两个整数相除。为了防止除数为零,它会测试第二个参数并在为零时设为失败:

class Div2ints : public ScalarFunction
{
public:
  virtual void processBlock(ServerInterface &srvInterface,
                            BlockReader &arg_reader,
                            BlockWriter &res_writer)
  {
    // While we have inputs to process
    do
      {
        const vint a = arg_reader.getIntRef(0);
        const vint b = arg_reader.getIntRef(1);
        if (b == 0)
          {
            vt_report_error(1,"Attempted divide by zero");
          }
        res_writer.setInt(a/b);
        res_writer.next();
      }
    while (arg_reader.next());
  }
};

加载和调用函数演示了用户所看到的错误。隔离和非隔离模式使用不同的错误号。

=> CREATE LIBRARY Div2IntsLib AS '/home/dbadmin/Div2ints.so';
CREATE LIBRARY
=> CREATE FUNCTION div2ints AS LANGUAGE 'C++' NAME 'Div2intsInfo' LIBRARY Div2IntsLib;
CREATE FUNCTION
=> SELECT div2ints(25, 5);
 div2ints
----------
        5
(1 row)
=> SELECT * FROM MyTable;
 a  | b
----+---
 12 | 6
  7 | 0
 12 | 2
 18 | 9
(4 rows)
=> SELECT * FROM MyTable WHERE div2ints(a, b) > 2;
ERROR 3399:  Error in calling processBlock() for User Defined Scalar Function
div2ints at Div2ints.cpp:21, error code: 1, message: Attempted divide by zero

在以下示例中,如果任一实参为 NULL,则 processBlock() 方法将引发异常:

@Override
public void processBlock(ServerInterface srvInterface,
                         BlockReader argReader,
                         BlockWriter resWriter)
            throws UdfException, DestroyInvocation
{
  do {
      // Test for NULL value. Throw exception if one occurs.
      if (argReader.isLongNull(0) || argReader.isLongNull(1) ) {
          // No nulls allowed. Throw exception
          throw new UdfException(1234, "Cannot add a NULL value");
     }

当 UDx 引发异常时,正在运行 UDx 的从属进程会将错误报告回 Vertica 并退出。Vertica 将向用户显示包含于异常中的错误消息和堆栈跟踪:

=> SELECT add2ints(2, NULL);
ERROR 3399:  Failure in UDx RPC call InvokeProcessBlock(): Error in User Defined Object [add2ints], error code: 1234
com.vertica.sdk.UdfException: Cannot add a NULL value
        at com.example.Add2intsFactory$Add2ints.processBlock(Add2intsFactory.java:37)
        at com.vertica.udxfence.UDxExecContext.processBlock(UDxExecContext.java:700)
        at com.vertica.udxfence.UDxExecContext.run(UDxExecContext.java:173)
        at java.lang.Thread.run(Thread.java:662)

在此示例中,如果其中一个实参小于 100,则 Python UDx 会引发错误:

    while(True):
        # Example of error checking best practices.
        product_id = block_reader.getInt(2)
        if product_id < 100:
            raise ValueError("Invalid Product ID")

错误会生成如下消息:

=> SELECT add2ints(prod_cost, sale_price, product_id) FROM bunch_of_numbers;
ERROR 3399:  Failure in UDx RPC call InvokeProcessBlock(): Error calling processBlock() in User Defined Object [add2ints]
at [/udx/PythonInterface.cpp:168], error code: 0,
message: Error [/udx/PythonInterface.cpp:385] function ['call_method']
(Python error type [<class 'ValueError'>])
Traceback (most recent call last):
  File "/home/dbadmin/py_db/v_py_db_node0001_catalog/Libraries/02fc4af0ace6f91eefa74baecf3ef76000a0000000004fc4/pylib_02fc4af0ace6f91eefa74baecf3ef76000a0000000004fc4.py",
line 13, in processBlock
    raise ValueError("Invalid Product ID")
ValueError: Invalid Product ID

在此示例中,如果数据帧的第三列与指定的产品 ID 不匹配,则 R UDx 会引发错误:


Calculate_Cost_w_Tax <- function(input.data.frame) {
  # Must match the Product ID 11444
  if ( !is.numeric(input.data.frame[, 3]) == 11444 ) {
    stop("Invalid Product ID!")
  } else {
    cost_w_tax <- data.frame(input.data.frame[, 1] * input.data.frame[, 2])
  }
  return(cost_w_tax)
}

Calculate_Cost_w_TaxFactory <- function() {
  list(name=Calculate_Cost_w_Tax,
       udxtype=c("scalar"),
       intype=c("float","float", "float"),
       outtype=c("float"))
}

错误会生成如下消息:

=> SELECT Calculate_Cost_w_Tax(item_price, tax_rate, prod_id) FROM Inventory_Sales_Data;
vsql:sql_test_multiply.sql:21: ERROR 3399:  Failure in UDx RPC call InvokeProcessBlock():
Error calling processBlock() in User Defined Object [mul] at
[/udx/RInterface.cpp:1308],
error code: 0, message: Exception in processBlock :Invalid Product ID!

要报告有关错误的其他诊断信息,您可以在引发异常之前将消息写入日志文件(请参阅日志)。

您的 UDx 不得使用其未引发的异常。拦截服务器异常可能会导致数据库不稳定。

3 - 日志

每个使用 C++、Java 或 Python 编写的 UDx 都有一个关联的 ServerInterface 实例。ServerInterface 类提供了写入 Vertica 日志的功能,C++ 实施还提供了在系统表中记录事件的功能。

将消息写入 Vertica 日志

可以使用 ServerInterface.log() 函数将消息写入到日志文件。此函数的工作方式与 printf() 相似,它使用已设置格式的字符串和一个可选值集,并将字符串写入到日志文件。将消息写入到的位置取决于函数在隔离模式还是非隔离模式下运行。

  • 在非隔离模式下运行的函数会将其消息写入到编录目录中的 vertica.log 文件。

  • 在隔离模式下运行的函数会将其消息写入到编录目录中名为 UDxLogs/UDxFencedProcesses.log 的日志文件。

为了帮助标识函数的输出,Vertica 会将绑定到 UDx 的 SQL 函数名称添加到日志消息中。

以下示例将记录 UDx 的输入值:

    virtual void processBlock(ServerInterface &srvInterface,
                              BlockReader &argReader,
                              BlockWriter &resWriter)
    {
        try {
            // While we have inputs to process
            do {
                if (argReader.isNull(0) || argReader.isNull(1)) {
                    resWriter.setNull();
                } else {
                    const vint a = argReader.getIntRef(0);
                    const vint b = argReader.getIntRef(1);
                    <span class="code-input">srvInterface.log("got a: %d and b: %d", (int) a, (int) b);</span>
                    resWriter.setInt(a+b);
                }
                resWriter.next();
            } while (argReader.next());
        } catch(std::exception& e) {
            // Standard exception. Quit.
            vt_report_error(0, "Exception while processing block: [%s]", e.what());
        }
    }
        @Override
        public void processBlock(ServerInterface srvInterface,
                                 BlockReader argReader,
                                 BlockWriter resWriter)
                    throws UdfException, DestroyInvocation
        {
            do {
                // Get the two integer arguments from the BlockReader
                long a = argReader.getLong(0);
                long b = argReader.getLong(1);

                // Log the input values
                <span class="code-input">srvInterface.log("Got values a=%d and b=%d", a, b);</span>

                long result = a+b;
                resWriter.setLong(result);
                resWriter.next();
            } while (argReader.next());
        }
    }
    def processBlock(self, server_interface, arg_reader, res_writer):
        server_interface.log("Python UDx - Adding 2 ints!")
        while(True):
            first_int = block_reader.getInt(0)
            second_int = block_reader.getInt(1)
            block_writer.setInt(first_int + second_int)
            <span class="code-input">server_interface.log("Values: first_int is {} second_int is {}".format(first_int, second_int))</span>
            block_writer.next()
            if not block_reader.next():
                break

log() 函数在日志文件中生成条目,如下所示:

$ tail /home/dbadmin/py_db/v_py_db_node0001_catalog/UDxLogs/UDxFencedProcesses.log
 07:52:12.862 [Python-v_py_db_node0001-7524:0x206c-40575]  0x7f70eee2f780 PythonExecContext::processBlock
 07:52:12.862 [Python-v_py_db_node0001-7524:0x206c-40575]  0x7f70eee2f780 [UserMessage] add2ints - Python UDx - Adding 2 ints!
 07:52:12.862 [Python-v_py_db_node0001-7524:0x206c-40575]  0x7f70eee2f780 [UserMessage] add2ints - Values: first_int is 100 second_int is 100

有关查看 Vertica 日志文件的详细信息,请参阅监控日志文件

将消息写入 UDX_EVENTS 表(仅限 C++)

在 C++ API 中,除了写入日志之外,您还可以将消息写入 UDX_EVENTS 系统表。写入系统表之后,您可以将来自所有节点的事件收集至一个位置中。

可以使用 ServerInterface.logEvent() 函数将消息写入此表。该函数将提取一个实参和一个映射。映射作为 Flex VMap 写入表的 RAW 列。以下示例显示 Parquet 导出器如何创建和记录此映射。

// Log exported parquet file details to v_monitor.udx_events
std::map<std::string, std::string> details;
details["file"] = escapedPath;
details["created"] = create_timestamp_;
details["closed"] = close_timestamp_;
details["rows"] = std::to_string(num_rows_in_file);
details["row_groups"] = std::to_string(num_row_groups_in_file);
details["size_mb"] = std::to_string((double)outputStream->Tell()/(1024*1024));
srvInterface.logEvent(details);

您可以从 VMap 中选择单个字段,如下例所示。

=> SELECT __RAW__['file'] FROM UDX_EVENTS;
                                   __RAW__
-----------------------------------------------------------------------------
 /tmp/export_tmpzLkrKq3a/450c4213-v_vmart_node0001-139770732459776-0.parquet
 /tmp/export_tmpzLkrKq3a/9df1c797-v_vmart_node0001-139770860660480-0.parquet
(2 rows)

或者,您可以定义一个视图,以便像列一样更轻松地直接查询字段。有关示例,请参阅 监控导出