Атрибут `#instrument` не работает должным образом

У меня есть следующая функция:

#[tracing::instrument(skip_all)]
pub(crate) async fn blocks_with_triggers(
    adapter: Arc<EthereumAdapter>,
    logger: Logger,
    chain_store: Arc<dyn ChainStore>,
    subgraph_metrics: Arc<SubgraphEthRpcMetrics>,
    from: BlockNumber,
    to: BlockNumber,
    filter: &TriggerFilter,
    unified_api_version: UnifiedMappingApiVersion,
) -> Result<Vec<BlockWithTriggers<crate::Chain>>, Error> {

    // lots of stuff

    let to_hash_fut = eth.block_hash_by_block_number(&logger, to)
        .and_then(|hash| match hash {
            Some(hash) => Ok(hash),
            None => {
                warn!(logger,
                      "Ethereum endpoint is behind";
                      "url" => eth.provider()
                );
                bail!("Block {} not found in the chain", to)
            }
        })
        .compat();

    // lots more stuff, eventually I wait on the above future
}

block_hash_by_block_number объявляется следующим образом:

#[tracing::instrument(skip_all)]
fn block_hash_by_block_number(
    &self,
    logger: &Logger,
    block_number: BlockNumber,
) -> Box<dyn Future<Item = Option<H256>, Error = Error> + Send> {
    // code that uses https://crates.io/crates/reqwest-tracing
}

Когда я запускаю эту программу, создаваемые трассировки не имеют имени промежутка block_hash_by_block_number под промежутком blocks_with_triggers. Вместо этого я рассматриваю интервалы POST как прямые дочерние элементы blocks_with_triggers, что нежелательно. Однако, если я изменю вызов на

let to_hash_span = info_span!("to_hash_future");
let to_hash_fut = {
    eth.block_hash_by_block_number(&logger, to)
        .and_then(|hash| match hash {
            Some(hash) => Ok(hash),
            None => {
                warn!(logger,
                      "Ethereum endpoint is behind";
                      "url" => eth.provider()
                );
                bail!("Block {} not found in the chain", to)
            }
        })
        .compat()
        .instrument(to_hash_span)
};

Тогда я вижу to_hash_future как прямого дочернего элемента blocks_with_triggers, а старые POST промежутки теперь являются дочерними элементами to_hash_future (но я все еще не рассматриваю block_hash_by_block_number как дочерний элемент to_hash_future).

Похоже, что в этом конкретном пути кода атрибут #instrument неправильно реализует функцию block_hash_by_block_number.

Вот как datadog собирает промежутки. Я ожидал бы, что отношения родитель-потомок будут to_hash_future -> block_hash_by_block_number -> POST -> yellow stuff, но вместо этого мы видим, что to_hash_future и block_hash_by_block_number являются братьями и сестрами.

Вот как я объявляю фильтр для своего приложения:

        let filter_layer =
            EnvFilter::try_new("graph_chain_ethereum,reqwest_tracing::reqwest_otel_span_builder")
                .unwrap();

Какой у вас фильтр уровня?

Chayim Friedman 31.07.2024 20:25

Добавлен фрагмент фильтра в OP, а также снимок экрана с интервалами, полученными datadog.

Paymahn Moghadasian 31.07.2024 20:29

Пожалуйста, предоставьте Минимально воспроизводимый пример.

Chayim Friedman 31.07.2024 20:34
Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
0
3
53
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

При использовании макроса instrument для функции созданный диапазон вводится только внутри этой функции.

block_hash_by_block_number не является функцией async, а представляет собой обычную функцию, возвращающую будущее, которое вы затем выполняете после выхода из диапазона.

Хотя эти два случая в большинстве случаев семантически одинаковы, макрос instrument действует по-разному в зависимости от того, является ли функция обычной функцией синхронизации или асинхронной функцией.

Макрос instrument для обычной функции работает следующим образом:

#[instrument]
fn foo() { /* fn body */ }
// gets turned into 
//    |
//    V
fn foo() { 
    let foo_span = info_span!("foo");
    let _guard = foo_span.enter();
    /* fn body */
    // span exits here
}

А на асинхронных функциях это работает так (не уверен, что именно это и получается, но скорее всего это хорошее приближение):

#[instrument]
async fn foo() { /* fn body */ }
// gets turned into 
//    |
//    V
fn foo() -> impl Future<...> { 
    let foo_span = info_span!("foo");
    let future = async { /* fn body */ }; 
    // span gets attached to the future, and is exited when that future completes
    return future.instrument(foo_span);
}

Если вы хотите, чтобы все события в этом будущем находились внутри диапазона, но при этом возвращали Box<dyn Future<...>>, вам следует либо создать новый диапазон, либо получить текущий диапазон с помощью tracing::Span::current() и вызвать .instrument(span), прежде чем возвращать это будущее из block_hash_by_block_number.

Я думаю, важно отметить, что block_hash_by_block_number возвращает будущее из ящика Futures 0.1. Это означает, что если добавление интервала в месте вызова неприемлемо, либо block_hash_by_block_number придется изменить подпись, чтобы вернуть будущее, совместимое со стандартом, либо необходимо будет добавить новую функцию (например, block_hash_by_block_number_compat) для вызова compat, а затем добавьте пролет.

Другие вопросы по теме