Proto parser in YTsaurus Flow (C++)
This example shows TProtoParsingProcessFunctionBase<TProto>, a base class for process functions that handles Protobuf message parsing. The pipeline reads serialized log records from a queue, parses them without calling ParseFromStringOrThrow manually, and maintains a state counter for parsed records of each level.
Pipeline components
TProtoLogParserFunction
TProtoLogParserFunction inherits from TProtoParsingProcessFunctionBase<TLogRecordProto>, where TLogRecordProto is a Protobuf message with the level and text fields. The base class reads the input column named by the data_column parameter (the "data" column by default), parses it into TLogRecordProto, and invokes one of the user methods:
ProcessProto(...): after successful parsing, gets the state for the message key, increments the level counter, builds aTLogRecordMessage, and emits it to therecordsstream;ProcessUnparsed(...): if the input column named by thedata_columnparameter is missing (null) orProtobufparsing fails. The implementation is empty, so such messages are silently dropped. An empty but present string parses successfully into aTLogRecordProtowith default values because the message has no required fields.
The process function is registered via YT_FLOW_DEFINE_PROCESS_FUNCTION. In the parser spec, TProcessFunctionTransformOrderedSourceComputation hosts it and processing_function contains NYT::NFlow::NExample::TProtoLogParserFunction. If necessary, the input column name can be set in processing_function_parameters/data_column.
The records output stream is configured with a direct external TSyncQueueSink, so no separate sink computation is required.
The TLevelCountsState state
TProtoLogParserFunction stores a TMutableStateKeyClient<TLevelCountsState> StateClient_ and initializes it in DoInit(const IRuntimeInitContextPtr&) via initContext->InitClient(StateClient_, "level_counts"). ProcessProto accesses the state through StateClient_.GetState(message->Key). The state contains record_counts, the number of parsed records of each level per source partition; the current value is written to the output message as seen_at_level.
TProcessFunctionTransformOrderedSourceComputation materializes output and persists state in the same epoch transaction that advances the source offset. This keeps the counter consistent with queue consumption across restarts.
Message types
TLogRecordMessage is a TYsonMessage subclass registered via YT_FLOW_DEFINE_YSON_MESSAGE with the following fields:
level: the record level fromTLogRecordProto;text: the record text fromTLogRecordProto;seen_at_level: the number of parsed records of this level in the source partition, including the current record.
The main function
The example library contains process-function and type registration. main initializes Flow, registers the records stream through TSimpleSpecBuilder, and starts TSimpleRunnerProgram.
Source code
TProtoLogParserFunction
#pragma once
#include <yt/yt/flow/examples/cpp/proto_parser/proto/log_record.pb.h>
#include <yt/yt/flow/library/cpp/common/state_client.h>
#include <yt/yt/flow/library/cpp/common/yson_message.h>
#include <yt/yt/flow/library/cpp/parsers/proto.h>
namespace NYT::NFlow::NExample {
////////////////////////////////////////////////////////////////////////////////
struct TLogRecordMessage
: public TYsonMessage
{
std::string Level;
std::string Text;
i64 SeenAtLevel = 0;
REGISTER_YSON_STRUCT(TLogRecordMessage);
static void Register(TRegistrar registrar);
};
////////////////////////////////////////////////////////////////////////////////
struct TLevelCountsState
: public NYTree::TYsonStruct
{
THashMap<std::string, i64> RecordCounts;
REGISTER_YSON_STRUCT(TLevelCountsState);
static void Register(TRegistrar registrar);
};
////////////////////////////////////////////////////////////////////////////////
class TProtoLogParserFunction
: public TProtoParsingProcessFunctionBase<
TLogRecordProto,
TProtoParsingProcessFunctionParameters,
/*PropagateHookErrors*/ true>
{
protected:
void DoInit(const IRuntimeInitContextPtr& initContext) override;
void ProcessProto(
const TInputMessageConstPtr& message,
TLogRecordProto&& proto,
const IOutputCollectorPtr& output,
const IRuntimeContextPtr& context) override;
void ProcessUnparsed(
const TInputMessageConstPtr& message,
TError error,
const IOutputCollectorPtr& output,
const IRuntimeContextPtr& context) override;
private:
TMutableStateKeyClient<TLevelCountsState> StateClient_;
};
////////////////////////////////////////////////////////////////////////////////
} // namespace NYT::NFlow::NExample
#include "proto_parser_function.h"
#include <yt/yt/flow/library/cpp/common/registry.h>
#include <yt/yt/flow/library/cpp/common/runtime_context.h>
#include <yt/yt/flow/library/cpp/common/runtime_init_context.h>
namespace NYT::NFlow::NExample {
////////////////////////////////////////////////////////////////////////////////
void TLogRecordMessage::Register(TRegistrar registrar)
{
registrar.Parameter("level", &TThis::Level)
.Default();
registrar.Parameter("text", &TThis::Text)
.Default();
registrar.Parameter("seen_at_level", &TThis::SeenAtLevel)
.Default();
}
void TLevelCountsState::Register(TRegistrar registrar)
{
registrar.Parameter("record_counts", &TThis::RecordCounts)
.Default();
}
////////////////////////////////////////////////////////////////////////////////
void TProtoLogParserFunction::DoInit(const IRuntimeInitContextPtr& initContext)
{
initContext->InitClient(StateClient_, "level_counts");
}
void TProtoLogParserFunction::ProcessProto(
const TInputMessageConstPtr& message,
TLogRecordProto&& proto,
const IOutputCollectorPtr& output,
const IRuntimeContextPtr& context)
{
auto state = StateClient_.GetState(message->Key);
auto record = New<TLogRecordMessage>();
record->Level = proto.level();
record->Text = proto.text();
record->SeenAtLevel = ++state->RecordCounts[proto.level()];
output->AddMessage(context->ConvertToMessage(record));
}
void TProtoLogParserFunction::ProcessUnparsed(
const TInputMessageConstPtr& /*message*/,
TError /*error*/,
const IOutputCollectorPtr& /*output*/,
const IRuntimeContextPtr& /*context*/)
{ }
////////////////////////////////////////////////////////////////////////////////
YT_FLOW_DEFINE_YSON_MESSAGE(TLogRecordMessage);
YT_FLOW_DEFINE_PROCESS_FUNCTION(TProtoLogParserFunction, TProtoParsingProcessFunctionParameters);
////////////////////////////////////////////////////////////////////////////////
} // namespace NYT::NFlow::NExample