Tracing for wasm with bridging to native (#6916)

* implement events handling, implement parent_id for spans & events

* add events to sp_io::storage

* update test

* add tests

* adjust limit

* let tracing crate handle parent_ids

* re-enable current-id tracking

* add test for threads with CurrentSpan

* fix log level

* remove redundant check for non wasm traces

* remove duplicate definition in test

* Adding conditional events API

* prefer explicit parent_id over current,

enhance test

* limit changes to client::tracing event implementation

* remove From impl due to fallback required on parent_id

* make tracing codecable

* replace with global tracing

* new tracing interface

* impl TracingSubscriber in client

* implement access to global TracingSubscriber from primitives

* span for wasm

* increment towards Wasm Tracing Subscriber implementation

* increment, remove sp-tracing from runtime-interface

* increment, it compiles

* attained original functionality with new mechanism

* implement remaining TracingSubscriber functions

* remove spans from decl_module

* add handling for encoded values

* Revert "replace with global tracing"

This reverts commit 8824a60deea54d9b437407a21c8ceaf6a1902ee5.

* Wasm Side Tracing

* tracing on wasm

* enable tracing wasm on node-runtime

* export all the macros in std

* tracing subscriber on wasm-side only

* pass spans and events over and record them

* reactivate previous code and  cleanup

* further cleaning up

* extend the span macros, activate through executive

* tracking the actual extrinsic, too

* style

* fixing tests

* spaces -> tabs

* attempting to reactivate params

* activate our tests in CI

* some passing

* tests passing

* with core lazy

* global tracer for wasm side with pass over

* fixing metadata referencing

* remove const_fn feature requirement

* reenable dispatch traces

* reset client tracing

* further cleaning up

* fixing runtime-test

* move tracing-build setup into runtime-test

* Merge DebugWriter from tracing and frame-support, move to sp-std

* remove dangling fixme

* Docs for tracing primitives

* cleaning up a bit more

* Wasm interface docs

* optimise docs.rs setup

* adding tracing flags to uncomment

* remove brace

* fixing imports

* fixing broken syntax

* add required modules

* nicer formatting

* better target management

* adding low level storage tracing events into frame

* add custom Debug impl for WasmMetadata

* cloning profiler

* adding info about cloning profiler

* using in-scope for within calls

* proper time tracing, cleaning up println

* allow to disable tracing on runtime_interface-macro

* disable tracing for wasm-tracing-interface

* simplify wasm-tracing-api

* update client to new interface

* fixing docs and tests for sp-tracing

* update integration tests

* re-activating enter_span

* dropping FIXME, it's documented

* fix formatting

* fix formatting

* fix imports

* more debug info

* inform wasm about it being disabled by returning 1

* only one tracer, but enabled multi-all support

* make trait pub again for tests

* Apply suggestions from code review

Co-authored-by: Niklas Adolfsson <niklasadolfsson1@gmail.com>

* fixing wasm doc tests for proper usage

* remove unnecessary import

* fixing formatting

* minor style fixes

* downgrading wabt

* update error message for UI

* Fix interface test

* next attempt to fix macros

* geee

* revert tracing on hashed for future PR

* remove local macros, use originals

* we are able to convert to static items

* implement more WasmValue types

* adding support to convert str, debug and encoded values

* more minor fixes

* revert unsafe 'static making

* fix indentation

* remove commented lines

* bump all them tracing versions

* cleaning up docs and info

* document new flag

* the new layered system handles span cloning better

* Apply suggestions from code review

Co-authored-by: David <dvdplm@gmail.com>

Co-authored-by: Matt Rutherford <mattrutherford@users.noreply.github.com>
Co-authored-by: Niklas Adolfsson <niklasadolfsson1@gmail.com>
Co-authored-by: David <dvdplm@gmail.com>
This commit is contained in:
Benjamin Kampmann
2020-09-18 08:10:59 +02:00
committed by GitHub
parent 649bee1a1e
commit a9c73113a8
34 changed files with 1207 additions and 436 deletions
+161 -49
View File
@@ -17,7 +17,7 @@
//! Substrate tracing primitives and macros.
//!
//! To trace functions or invidual code in Substrate, this crate provides [`tracing_span`]
//! To trace functions or invidual code in Substrate, this crate provides [`within_span`]
//! and [`enter_span`]. See the individual docs for how to use these macros.
//!
//! Note that to allow traces from wasm execution environment there are
@@ -28,21 +28,80 @@
//! Additionally, we have a const: `WASM_TRACE_IDENTIFIER`, which holds a span name used
//! to signal that the 'actual' span name and target should be retrieved instead from
//! the associated Fields mentioned above.
#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(feature = "std")]
#[macro_use]
extern crate rental;
/// Tracing facilities and helpers.
///
/// This is modeled after the `tracing`/`tracing-core` interface and uses that more or
/// less directly for the native side. Because of certain optimisations the these crates
/// have done, the wasm implementation diverges slightly and is optimised for thtat use
/// case (like being able to cross the wasm/native boundary via scale codecs).
///
/// One of said optimisations is that all macros will yield to a `noop` in non-std unless
/// the `with-tracing` feature is explicitly activated. This allows you to just use the
/// tracing wherever you deem fit and without any performance impact by default. Only if
/// the specific `with-tracing`-feature is activated on this crate will it actually include
/// the tracing code in the non-std environment.
///
/// Because of that optimisation, you should not use the `span!` and `span_*!` macros
/// directly as they yield nothing without the feature present. Instead you should use
/// `enter_span!` and `within_span!` which would strip away even any parameter conversion
/// you do within the span-definition (and thus optimise your performance). For your
/// convineience you directly specify the `Level` and name of the span or use the full
/// feature set of `span!`/`span_*!` on it:
///
/// # Example
///
/// ```rust
/// sp_tracing::enter_span!(sp_tracing::Level::TRACE, "fn wide span");
/// {
/// sp_tracing::enter_span!(sp_tracing::trace_span!("outer-span"));
/// {
/// sp_tracing::enter_span!(sp_tracing::Level::TRACE, "inner-span");
/// // ..
/// } // inner span exists here
/// } // outer span exists here
///
/// sp_tracing::within_span! {
/// sp_tracing::debug_span!("debug-span", you_can_pass="any params");
/// 1 + 1;
/// // some other complex code
/// } // debug span ends here
///
/// ```
///
///
/// # Setup
///
/// This project only provides the macros and facilities to manage tracing
/// it doesn't implement the tracing subscriber or backend directly that is
/// up to the developer integrating it into a specific environment. In native
/// this can and must be done through the regular `tracing`-facitilies, please
/// see their documentation for details.
///
/// On the wasm-side we've adopted a similar approach of having a global
/// `TracingSubscriber` that the macros call and that does the actual work
/// of tracking. To provide your tracking, you must implement `TracingSubscriber`
/// and call `set_tracing_subscriber` at the very beginning of your execution
/// the default subscriber is doing nothing, so any spans or events happening before
/// will not be recorded!
///
mod types;
#[cfg(feature = "std")]
#[doc(hidden)]
pub use tracing;
use tracing;
#[cfg(feature = "std")]
pub mod proxy;
pub use tracing::{
debug, debug_span, error, error_span, info, info_span, trace, trace_span, warn, warn_span,
span, event, Level, Span,
};
pub use crate::types::{
WasmMetadata, WasmEntryAttributes, WasmValuesSet, WasmValue, WasmFields, WasmLevel, WasmFieldName
};
#[cfg(feature = "std")]
use std::sync::atomic::{AtomicBool, Ordering};
/// Try to init a simple tracing subscriber with log compatibility layer.
/// Ignores any error. Useful for testing.
@@ -51,74 +110,127 @@ pub fn try_init_simple() {
let _ = tracing_subscriber::fmt().with_writer(std::io::stderr).try_init();
}
/// Flag to signal whether to run wasm tracing
#[cfg(feature = "std")]
static WASM_TRACING_ENABLED: AtomicBool = AtomicBool::new(false);
pub use crate::types::{
WASM_NAME_KEY, WASM_TARGET_KEY, WASM_TRACE_IDENTIFIER
};
/// Runs given code within a tracing span, measuring it's execution time.
///
/// If tracing is not enabled, the code is still executed.
/// If tracing is not enabled, the code is still executed. Pass in level and name or
/// use any valid `sp_tracing::Span`followe by `;` and the code to execute,
///
/// # Example
///
/// ```
/// sp_tracing::tracing_span! {
/// sp_tracing::within_span! {
/// sp_tracing::Level::TRACE,
/// "test-span";
/// 1 + 1;
/// // some other complex code
/// }
///
/// sp_tracing::within_span! {
/// sp_tracing::span!(sp_tracing::Level::WARN, "warn-span", you_can_pass="any params");
/// 1 + 1;
/// // some other complex code
/// }
///
/// sp_tracing::within_span! {
/// sp_tracing::debug_span!("debug-span", you_can_pass="any params");
/// 1 + 1;
/// // some other complex code
/// }
/// ```
#[cfg(any(feature = "std", feature = "with-tracing"))]
#[macro_export]
macro_rules! tracing_span {
macro_rules! within_span {
(
$span:expr;
$( $code:tt )*
) => {
$span.in_scope(||
{
$( $code )*
}
)
};
(
$lvl:expr,
$name:expr;
$( $code:tt )*
) => {
{
$crate::enter_span!($name);
$( $code )*
$crate::within_span!($crate::span!($crate::Level::TRACE, $name); $( $code )*)
}
}
};
}
#[cfg(all(not(feature = "std"), not(feature = "with-tracing")))]
#[macro_export]
macro_rules! within_span {
(
$span:stmt;
$( $code:tt )*
) => {
$( $code )*
};
(
$lvl:expr,
$name:expr;
$( $code:tt )*
) => {
$( $code )*
};
}
/// Enter a span - noop for `no_std` without `with-tracing`
#[cfg(all(not(feature = "std"), not(feature = "with-tracing")))]
#[macro_export]
macro_rules! enter_span {
( $lvl:expr, $name:expr ) => ( );
( $name:expr ) => ( ) // no-op
}
/// Enter a span.
///
/// The span will be valid, until the scope is left.
/// The span will be valid, until the scope is left. Use either level and name
/// or pass in any valid `sp_tracing::Span` for extended usage. The span will
/// be exited on drop which is at the end of the block or to the next
/// `enter_span!` calls, as this overwrites the local variable. For nested
/// usage or to ensure the span closes at certain time either put it into a block
/// or use `within_span!`
///
/// # Example
///
/// ```
/// sp_tracing::enter_span!("test-span");
/// sp_tracing::enter_span!(sp_tracing::Level::TRACE, "test-span");
/// // previous will be dropped here
/// sp_tracing::enter_span!(
/// sp_tracing::span!(sp_tracing::Level::DEBUG, "debug-span", params="value"));
/// sp_tracing::enter_span!(sp_tracing::info_span!("info-span", params="value"));
///
/// {
/// sp_tracing::enter_span!(sp_tracing::Level::TRACE, "outer-span");
/// {
/// sp_tracing::enter_span!(sp_tracing::Level::TRACE, "inner-span");
/// // ..
/// } // inner span exists here
/// } // outer span exists here
///
/// ```
#[cfg(any(feature = "std", feature = "with-tracing"))]
#[macro_export]
macro_rules! enter_span {
( $name:expr ) => {
let __tracing_span__ = $crate::if_tracing!(
$crate::tracing::span!($crate::tracing::Level::TRACE, $name)
);
let __tracing_guard__ = $crate::if_tracing!(__tracing_span__.enter());
}
}
/// Generates the given code if the tracing dependency is enabled.
#[macro_export]
#[cfg(feature = "std")]
macro_rules! if_tracing {
( $if:expr ) => {{ $if }}
}
#[macro_export]
#[cfg(not(feature = "std"))]
macro_rules! if_tracing {
( $if:expr ) => {{}}
}
#[cfg(feature = "std")]
pub fn wasm_tracing_enabled() -> bool {
WASM_TRACING_ENABLED.load(Ordering::Relaxed)
}
#[cfg(feature = "std")]
pub fn set_wasm_tracing(b: bool) {
WASM_TRACING_ENABLED.store(b, Ordering::Relaxed)
( $span:expr ) => {
// Calling this twice in a row will overwrite (and drop) the earlier
// that is a _documented feature_!
let __within_span__ = $span;
let __tracing_guard__ = __within_span__.enter();
};
( $lvl:expr, $name:expr ) => {
$crate::enter_span!($crate::span!($crate::Level::TRACE, $name))
};
}
-165
View File
@@ -1,165 +0,0 @@
// Copyright 2020 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate 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 3 of the License, or
// (at your option) any later version.
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
//! Proxy to allow entering tracing spans from wasm.
//!
//! Use `enter_span` and `exit_span` to surround the code that you wish to trace
use rental;
use tracing::info_span;
/// Used to identify a proxied WASM trace
pub const WASM_TRACE_IDENTIFIER: &'static str = "WASM_TRACE";
/// Used to extract the real `target` from the associated values of the span
pub const WASM_TARGET_KEY: &'static str = "proxied_wasm_target";
/// Used to extract the real `name` from the associated values of the span
pub const WASM_NAME_KEY: &'static str = "proxied_wasm_name";
const MAX_SPANS_LEN: usize = 1000;
rental! {
pub mod rent_span {
#[rental]
pub struct SpanAndGuard {
span: Box<tracing::Span>,
guard: tracing::span::Entered<'span>,
}
}
}
/// Requires a tracing::Subscriber to process span traces,
/// this is available when running with client (and relevant cli params).
pub struct TracingProxy {
next_id: u64,
spans: Vec<(u64, rent_span::SpanAndGuard)>,
}
impl Drop for TracingProxy {
fn drop(&mut self) {
if !self.spans.is_empty() {
log::debug!(
target: "tracing",
"Dropping TracingProxy with {} un-exited spans, marking as not valid", self.spans.len()
);
while let Some((_, mut sg)) = self.spans.pop() {
sg.rent_all_mut(|s| { s.span.record("is_valid_trace", &false); });
}
}
}
}
impl TracingProxy {
pub fn new() -> TracingProxy {
TracingProxy {
next_id: 0,
spans: Vec::new(),
}
}
}
impl TracingProxy {
/// Create and enter a `tracing` Span, returning the span id,
/// which should be passed to `exit_span(id)` to signal that the span should exit.
pub fn enter_span(&mut self, proxied_wasm_target: &str, proxied_wasm_name: &str) -> u64 {
// The identifiers `proxied_wasm_target` and `proxied_wasm_name` must match their associated const,
// WASM_TARGET_KEY and WASM_NAME_KEY.
let span = info_span!(WASM_TRACE_IDENTIFIER, is_valid_trace = true, proxied_wasm_target, proxied_wasm_name);
self.next_id += 1;
let sg = rent_span::SpanAndGuard::new(
Box::new(span),
|span| span.enter(),
);
self.spans.push((self.next_id, sg));
if self.spans.len() > MAX_SPANS_LEN {
// This is to prevent unbounded growth of Vec and could mean one of the following:
// 1. Too many nested spans, or MAX_SPANS_LEN is too low.
// 2. Not correctly exiting spans due to misconfiguration / misuse
log::warn!(
target: "tracing",
"TracingProxy MAX_SPANS_LEN exceeded, removing oldest span."
);
let mut sg = self.spans.remove(0).1;
sg.rent_all_mut(|s| { s.span.record("is_valid_trace", &false); });
}
self.next_id
}
/// Exit a span by dropping it along with it's associated guard.
pub fn exit_span(&mut self, id: u64) {
if self.spans.last().map(|l| id > l.0).unwrap_or(true) {
log::warn!(target: "tracing", "Span id not found in TracingProxy: {}", id);
return;
}
let mut last_span = self.spans.pop().expect("Just checked that there is an element to pop; qed");
while id < last_span.0 {
log::warn!(
target: "tracing",
"TracingProxy Span ids not equal! id parameter given: {}, last span: {}",
id,
last_span.0,
);
last_span.1.rent_all_mut(|s| { s.span.record("is_valid_trace", &false); });
if let Some(s) = self.spans.pop() {
last_span = s;
} else {
log::warn!(target: "tracing", "Span id not found in TracingProxy {}", id);
return;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn create_spans(proxy: &mut TracingProxy, qty: usize) -> Vec<u64> {
let mut spans = Vec::new();
for n in 0..qty {
spans.push(proxy.enter_span("target", &format!("{}", n)));
}
spans
}
#[test]
fn max_spans_len_respected() {
let mut proxy = TracingProxy::new();
let _spans = create_spans(&mut proxy, MAX_SPANS_LEN + 10);
assert_eq!(proxy.spans.len(), MAX_SPANS_LEN);
// ensure oldest spans removed
assert_eq!(proxy.spans[0].0, 11);
}
#[test]
fn handles_span_exit_scenarios() {
let mut proxy = TracingProxy::new();
let _spans = create_spans(&mut proxy, 10);
assert_eq!(proxy.spans.len(), 10);
// exit span normally
proxy.exit_span(10);
assert_eq!(proxy.spans.len(), 9);
// skip and exit outer span without exiting inner, id: 8 instead of 9
proxy.exit_span(8);
// should have also removed the inner span that was lost
assert_eq!(proxy.spans.len(), 7);
// try to exit span not held
proxy.exit_span(9);
assert_eq!(proxy.spans.len(), 7);
// exit all spans
proxy.exit_span(1);
assert_eq!(proxy.spans.len(), 0);
}
}
+623
View File
@@ -0,0 +1,623 @@
// This file is part of Substrate.
// Copyright (C) 2020 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/// Types for wasm based tracing. Loosly inspired by `tracing-core` but
/// optimised for the specific use case.
use core::{format_args, fmt::Debug};
use sp_std::{
vec, vec::Vec,
};
use sp_std::Writer;
use codec::{Encode, Decode};
/// The Tracing Level the user can filter by this
#[derive(Clone, Encode, Decode, Debug)]
pub enum WasmLevel {
/// This is a fatal errors
ERROR,
/// This is a warning you should be aware of
WARN,
/// Nice to now info
INFO,
/// Further information for debugging purposes
DEBUG,
/// The lowest level, keeping track of minute detail
TRACE
}
impl From<&tracing_core::Level> for WasmLevel {
fn from(l: &tracing_core::Level) -> WasmLevel {
match l {
&tracing_core::Level::ERROR => WasmLevel::ERROR,
&tracing_core::Level::WARN => WasmLevel::WARN,
&tracing_core::Level::INFO => WasmLevel::INFO,
&tracing_core::Level::DEBUG => WasmLevel::DEBUG,
&tracing_core::Level::TRACE => WasmLevel::TRACE,
}
}
}
impl core::default::Default for WasmLevel {
fn default() -> Self {
WasmLevel::TRACE
}
}
/// A paramter value provided to the span/event
#[derive(Encode, Decode, Clone)]
pub enum WasmValue {
U8(u8),
I8(i8),
U32(u32),
I32(i32),
I64(i64),
U64(u64),
Bool(bool),
Str(Vec<u8>),
/// Debug or Display call, this is most-likely a print-able UTF8 String
Formatted(Vec<u8>),
/// SCALE CODEC encoded object the name should allow the received to know
/// how to decode this.
Encoded(Vec<u8>),
}
impl core::fmt::Debug for WasmValue {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
match self {
WasmValue::U8(ref i) => {
f.write_fmt(format_args!("{}_u8", i))
}
WasmValue::I8(ref i) => {
f.write_fmt(format_args!("{}_i8", i))
}
WasmValue::U32(ref i) => {
f.write_fmt(format_args!("{}_u32", i))
}
WasmValue::I32(ref i) => {
f.write_fmt(format_args!("{}_i32", i))
}
WasmValue::I64(ref i) => {
f.write_fmt(format_args!("{}_i64", i))
}
WasmValue::U64(ref i) => {
f.write_fmt(format_args!("{}_u64", i))
}
WasmValue::Bool(ref i) => {
f.write_fmt(format_args!("{}_bool", i))
}
WasmValue::Formatted(ref i) | WasmValue::Str(ref i) => {
if let Ok(v) = core::str::from_utf8(i) {
f.write_fmt(format_args!("{}", v))
} else {
f.write_fmt(format_args!("{:?}", i))
}
}
WasmValue::Encoded(ref v) => {
f.write_str("Scale(")?;
for byte in v {
f.write_fmt(format_args!("{:02x}", byte))?;
}
f.write_str(")")
}
}
}
}
impl From<u8> for WasmValue {
fn from(u: u8) -> WasmValue {
WasmValue::U8(u)
}
}
impl From<&i8> for WasmValue {
fn from(inp: &i8) -> WasmValue {
WasmValue::I8(inp.clone())
}
}
impl From<&str> for WasmValue {
fn from(inp: &str) -> WasmValue {
WasmValue::Str(inp.as_bytes().to_vec())
}
}
impl From<&&str> for WasmValue {
fn from(inp: &&str) -> WasmValue {
WasmValue::Str((*inp).as_bytes().to_vec())
}
}
impl From<bool> for WasmValue {
fn from(inp: bool) -> WasmValue {
WasmValue::Bool(inp)
}
}
impl From<core::fmt::Arguments<'_>> for WasmValue {
fn from(inp: core::fmt::Arguments<'_>) -> WasmValue {
let mut buf = Writer::default();
core::fmt::write(&mut buf, inp).expect("Writing of arguments doesn't fail");
WasmValue::Formatted(buf.into_inner())
}
}
impl From<i8> for WasmValue {
fn from(u: i8) -> WasmValue {
WasmValue::I8(u)
}
}
impl From<i32> for WasmValue {
fn from(u: i32) -> WasmValue {
WasmValue::I32(u)
}
}
impl From<&i32> for WasmValue {
fn from(u: &i32) -> WasmValue {
WasmValue::I32(*u)
}
}
impl From<u32> for WasmValue {
fn from(u: u32) -> WasmValue {
WasmValue::U32(u)
}
}
impl From<&u32> for WasmValue {
fn from(u: &u32) -> WasmValue {
WasmValue::U32(*u)
}
}
impl From<u64> for WasmValue {
fn from(u: u64) -> WasmValue {
WasmValue::U64(u)
}
}
impl From<i64> for WasmValue {
fn from(u: i64) -> WasmValue {
WasmValue::I64(u)
}
}
/// The name of a field provided as the argument name when contstructing an
/// `event!` or `span!`.
/// Generally generated automaticaly via `stringify` from an `'static &str`.
/// Likely print-able.
#[derive(Encode, Decode, Clone)]
pub struct WasmFieldName(Vec<u8>);
impl core::fmt::Debug for WasmFieldName {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
if let Ok(v) = core::str::from_utf8(&self.0) {
f.write_fmt(format_args!("{}", v))
} else {
for byte in self.0.iter() {
f.write_fmt(format_args!("{:02x}", byte))?;
}
Ok(())
}
}
}
impl From<Vec<u8>> for WasmFieldName {
fn from(v: Vec<u8>) -> Self {
WasmFieldName(v)
}
}
impl From<&str> for WasmFieldName {
fn from(v: &str) -> Self {
WasmFieldName(v.as_bytes().to_vec())
}
}
/// A list of `WasmFieldName`s in the order provided
#[derive(Encode, Decode, Clone, Debug)]
pub struct WasmFields(Vec<WasmFieldName>);
impl WasmFields {
/// Iterate over the fields
pub fn iter(&self) -> core::slice::Iter<'_, WasmFieldName> {
self.0.iter()
}
}
impl From<Vec<WasmFieldName>> for WasmFields {
fn from(v: Vec<WasmFieldName>) -> WasmFields {
WasmFields(v.into())
}
}
impl From<Vec<&str>> for WasmFields {
fn from(v: Vec<&str>) -> WasmFields {
WasmFields(v.into_iter().map(|v| v.into()).collect())
}
}
impl WasmFields {
/// Create an empty entry
pub fn empty() -> Self {
WasmFields(Vec::with_capacity(0))
}
}
impl From<&tracing_core::field::FieldSet> for WasmFields {
fn from(wm: &tracing_core::field::FieldSet) -> WasmFields {
WasmFields(wm.iter().map(|s| s.name().into()).collect())
}
}
/// A list of `WasmFieldName`s with the given `WasmValue` (if provided)
/// in the order specified.
#[derive(Encode, Decode, Clone)]
pub struct WasmValuesSet(Vec<(WasmFieldName, Option<WasmValue>)>);
impl core::fmt::Debug for WasmValuesSet {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
let mut wrt = f.debug_struct("");
let mut non_str = false;
for (f, v) in self.0.iter() {
if let Ok(s) = core::str::from_utf8(&f.0) {
match v {
Some(ref i) => wrt.field(s, i),
None => wrt.field(s, &(None as Option<WasmValue>)),
};
} else {
non_str = true;
}
}
// FIXME: replace with using `finish_non_exhaustive()` once stable
// https://github.com/rust-lang/rust/issues/67364
if non_str {
wrt.field("..", &"..");
}
wrt.finish()
}
}
impl From<Vec<(WasmFieldName, Option<WasmValue>)>> for WasmValuesSet {
fn from(v: Vec<(WasmFieldName, Option<WasmValue>)>) -> Self {
WasmValuesSet(v)
}
}
impl From<Vec<(&&WasmFieldName, Option<WasmValue>)>> for WasmValuesSet {
fn from(v: Vec<(&&WasmFieldName, Option<WasmValue>)>) -> Self {
WasmValuesSet(v.into_iter().map(|(k, v)| ((**k).clone(), v)).collect())
}
}
impl From<Vec<(&&str, Option<WasmValue>)>> for WasmValuesSet {
fn from(v: Vec<(&&str, Option<WasmValue>)>) -> Self {
WasmValuesSet(v.into_iter().map(|(k, v)| ((*k).into(), v)).collect())
}
}
impl WasmValuesSet {
/// Create an empty entry
pub fn empty() -> Self {
WasmValuesSet(Vec::with_capacity(0))
}
}
impl tracing_core::field::Visit for WasmValuesSet {
fn record_debug(&mut self, field: &tracing_core::field::Field, value: &dyn Debug) {
self.0.push( (
field.name().into(),
Some(WasmValue::from(format_args!("{:?}", value)))
))
}
fn record_i64(&mut self, field: &tracing_core::field::Field, value: i64) {
self.0.push( (
field.name().into(),
Some(WasmValue::from(value))
))
}
fn record_u64(&mut self, field: &tracing_core::field::Field, value: u64) {
self.0.push( (
field.name().into(),
Some(WasmValue::from(value))
))
}
fn record_bool(&mut self, field: &tracing_core::field::Field, value: bool) {
self.0.push( (
field.name().into(),
Some(WasmValue::from(value))
))
}
fn record_str(&mut self, field: &tracing_core::field::Field, value: &str) {
self.0.push( (
field.name().into(),
Some(WasmValue::from(value))
))
}
}
/// Metadata provides generic information about the specifc location of the
/// `span!` or `event!` call on the wasm-side.
#[derive(Encode, Decode, Clone)]
pub struct WasmMetadata {
/// The name given to `event!`/`span!`, `&'static str` converted to bytes
pub name: Vec<u8>,
/// The given target to `event!`/`span!` or module-name, `&'static str` converted to bytes
pub target: Vec<u8>,
/// The level of this entry
pub level: WasmLevel,
/// The file this was emitted from useful for debugging; `&'static str` converted to bytes
pub file: Vec<u8>,
/// The specific line number in the file useful for debugging
pub line: u32,
/// The module path; `&'static str` converted to bytes
pub module_path: Vec<u8>,
/// Whether this is a call to `span!` or `event!`
pub is_span: bool,
/// The list of fields specified in the call
pub fields: WasmFields,
}
impl From<&tracing_core::Metadata<'_>> for WasmMetadata {
fn from(wm: &tracing_core::Metadata<'_>) -> WasmMetadata {
WasmMetadata {
name: wm.name().as_bytes().to_vec(),
target: wm.target().as_bytes().to_vec(),
level: wm.level().into(),
file: wm.file().map(|f| f.as_bytes().to_vec()).unwrap_or_default(),
line: wm.line().unwrap_or_default(),
module_path: wm.module_path().map(|m| m.as_bytes().to_vec()).unwrap_or_default(),
is_span: wm.is_span(),
fields: wm.fields().into()
}
}
}
impl core::fmt::Debug for WasmMetadata {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("WasmMetadata")
.field("name", &decode_field(&self.name))
.field("target", &decode_field(&self.target))
.field("level", &self.level)
.field("file", &decode_field(&self.file))
.field("line", &self.line)
.field("module_path", &decode_field(&self.module_path))
.field("is_span", &self.is_span)
.field("fields", &self.fields)
.finish()
}
}
impl core::default::Default for WasmMetadata {
fn default() -> Self {
let target = "default".as_bytes().to_vec();
WasmMetadata {
target,
name: Default::default(),
level: Default::default(),
file: Default::default(),
line: Default::default(),
module_path: Default::default(),
is_span: true,
fields: WasmFields::empty()
}
}
}
fn decode_field(field: &[u8]) -> &str {
core::str::from_utf8(field).unwrap_or_default()
}
/// Span or Event Attributes
#[derive(Encode, Decode, Clone, Debug)]
pub struct WasmEntryAttributes {
/// the parent, if directly specified otherwise assume most inner span
pub parent_id: Option<u64>,
/// the metadata of the location
pub metadata: WasmMetadata,
/// the Values provided
pub fields: WasmValuesSet,
}
impl From<&tracing_core::Event<'_>> for WasmEntryAttributes {
fn from(evt: &tracing_core::Event<'_>) -> WasmEntryAttributes {
let mut fields = WasmValuesSet(Vec::new());
evt.record(&mut fields);
WasmEntryAttributes {
parent_id: evt.parent().map(|id| id.into_u64()),
metadata: evt.metadata().into(),
fields: fields
}
}
}
impl From<&tracing_core::span::Attributes<'_>> for WasmEntryAttributes {
fn from(attrs: &tracing_core::span::Attributes<'_>) -> WasmEntryAttributes {
let mut fields = WasmValuesSet(Vec::new());
attrs.record(&mut fields);
WasmEntryAttributes {
parent_id: attrs.parent().map(|id| id.into_u64()),
metadata: attrs.metadata().into(),
fields: fields
}
}
}
impl core::default::Default for WasmEntryAttributes {
fn default() -> Self {
WasmEntryAttributes {
parent_id: None,
metadata: Default::default(),
fields: WasmValuesSet(vec![]),
}
}
}
#[cfg(feature = "std")]
mod std_features {
use tracing_core::callsite;
use tracing;
/// Static entry use for wasm-originated metadata.
pub struct WasmCallsite;
impl callsite::Callsite for WasmCallsite {
fn set_interest(&self, _: tracing_core::Interest) { unimplemented!() }
fn metadata(&self) -> &tracing_core::Metadata { unimplemented!() }
}
static CALLSITE: WasmCallsite = WasmCallsite;
/// The identifier we are using to inject the wasm events in the generic `tracing` system
pub static WASM_TRACE_IDENTIFIER: &'static str = "wasm_tracing";
/// The fieldname for the wasm-originated name
pub static WASM_NAME_KEY: &'static str = "name";
/// The fieldname for the wasm-originated target
pub static WASM_TARGET_KEY: &'static str = "target";
/// The the list of all static field names we construct from the given metadata
pub static GENERIC_FIELDS: &'static [&'static str] = &[WASM_TARGET_KEY, WASM_NAME_KEY,
"file", "line", "module_path", "params"];
// Implementation Note:
// the original `tracing` crate generates these static metadata entries at every `span!` and
// `event!` location to allow for highly optimised filtering. For us to allow level-based emitting
// of wasm events we need these static metadata entries to inject into that system. We then provide
// generic `From`-implementations picking the right metadata to refer to.
static SPAN_ERROR_METADATA : tracing_core::Metadata<'static> = tracing::Metadata::new(
WASM_TRACE_IDENTIFIER, WASM_TRACE_IDENTIFIER, tracing::Level::ERROR, None, None, None,
tracing_core::field::FieldSet::new(GENERIC_FIELDS, tracing_core::identify_callsite!(&CALLSITE)),
tracing_core::metadata::Kind::SPAN
);
static SPAN_WARN_METADATA : tracing_core::Metadata<'static> = tracing::Metadata::new(
WASM_TRACE_IDENTIFIER, WASM_TRACE_IDENTIFIER, tracing::Level::WARN, None, None, None,
tracing_core::field::FieldSet::new(GENERIC_FIELDS, tracing_core::identify_callsite!(&CALLSITE)),
tracing_core::metadata::Kind::SPAN
);
static SPAN_INFO_METADATA : tracing_core::Metadata<'static> = tracing::Metadata::new(
WASM_TRACE_IDENTIFIER, WASM_TRACE_IDENTIFIER, tracing::Level::INFO, None, None, None,
tracing_core::field::FieldSet::new(GENERIC_FIELDS, tracing_core::identify_callsite!(&CALLSITE)),
tracing_core::metadata::Kind::SPAN
);
static SPAN_DEBUG_METADATA : tracing_core::Metadata<'static> = tracing::Metadata::new(
WASM_TRACE_IDENTIFIER, WASM_TRACE_IDENTIFIER, tracing::Level::DEBUG, None, None, None,
tracing_core::field::FieldSet::new(GENERIC_FIELDS, tracing_core::identify_callsite!(&CALLSITE)),
tracing_core::metadata::Kind::SPAN
);
static SPAN_TRACE_METADATA : tracing_core::Metadata<'static> = tracing::Metadata::new(
WASM_TRACE_IDENTIFIER, WASM_TRACE_IDENTIFIER, tracing::Level::TRACE, None, None, None,
tracing_core::field::FieldSet::new(GENERIC_FIELDS, tracing_core::identify_callsite!(&CALLSITE)),
tracing_core::metadata::Kind::SPAN
);
static EVENT_ERROR_METADATA : tracing_core::Metadata<'static> = tracing::Metadata::new(
WASM_TRACE_IDENTIFIER, WASM_TRACE_IDENTIFIER, tracing::Level::ERROR, None, None, None,
tracing_core::field::FieldSet::new(GENERIC_FIELDS, tracing_core::identify_callsite!(&CALLSITE)),
tracing_core::metadata::Kind::EVENT
);
static EVENT_WARN_METADATA : tracing_core::Metadata<'static> = tracing::Metadata::new(
WASM_TRACE_IDENTIFIER, WASM_TRACE_IDENTIFIER, tracing::Level::WARN, None, None, None,
tracing_core::field::FieldSet::new(GENERIC_FIELDS, tracing_core::identify_callsite!(&CALLSITE)),
tracing_core::metadata::Kind::EVENT
);
static EVENT_INFO_METADATA : tracing_core::Metadata<'static> = tracing::Metadata::new(
WASM_TRACE_IDENTIFIER, WASM_TRACE_IDENTIFIER, tracing::Level::INFO, None, None, None,
tracing_core::field::FieldSet::new(GENERIC_FIELDS, tracing_core::identify_callsite!(&CALLSITE)),
tracing_core::metadata::Kind::EVENT
);
static EVENT_DEBUG_METADATA : tracing_core::Metadata<'static> = tracing::Metadata::new(
WASM_TRACE_IDENTIFIER, WASM_TRACE_IDENTIFIER, tracing::Level::DEBUG, None, None, None,
tracing_core::field::FieldSet::new(GENERIC_FIELDS, tracing_core::identify_callsite!(&CALLSITE)),
tracing_core::metadata::Kind::EVENT
);
static EVENT_TRACE_METADATA : tracing_core::Metadata<'static> = tracing::Metadata::new(
WASM_TRACE_IDENTIFIER, WASM_TRACE_IDENTIFIER, tracing::Level::TRACE, None, None, None,
tracing_core::field::FieldSet::new(GENERIC_FIELDS, tracing_core::identify_callsite!(&CALLSITE)),
tracing_core::metadata::Kind::EVENT
);
// FIXME: this could be done a lot in 0.2 if they opt for using `Cow<str,'static>` instead
// https://github.com/paritytech/substrate/issues/7134
impl From<&crate::WasmMetadata> for &'static tracing_core::Metadata<'static> {
fn from(wm: &crate::WasmMetadata) -> &'static tracing_core::Metadata<'static> {
match (&wm.level, wm.is_span) {
(&crate::WasmLevel::ERROR, true) => &SPAN_ERROR_METADATA,
(&crate::WasmLevel::WARN, true) => &SPAN_WARN_METADATA,
(&crate::WasmLevel::INFO, true) => &SPAN_INFO_METADATA,
(&crate::WasmLevel::DEBUG, true) => &SPAN_DEBUG_METADATA,
(&crate::WasmLevel::TRACE, true) => &SPAN_TRACE_METADATA,
(&crate::WasmLevel::ERROR, false) => &EVENT_ERROR_METADATA,
(&crate::WasmLevel::WARN, false) => &EVENT_WARN_METADATA,
(&crate::WasmLevel::INFO, false) => &EVENT_INFO_METADATA,
(&crate::WasmLevel::DEBUG, false) => &EVENT_DEBUG_METADATA,
(&crate::WasmLevel::TRACE, false) => &EVENT_TRACE_METADATA,
}
}
}
impl From<crate::WasmEntryAttributes> for tracing::Span {
fn from(a: crate::WasmEntryAttributes) -> tracing::Span {
let name = std::str::from_utf8(&a.metadata.name).unwrap_or_default();
let target = std::str::from_utf8(&a.metadata.target).unwrap_or_default();
let file = std::str::from_utf8(&a.metadata.file).unwrap_or_default();
let line = a.metadata.line;
let module_path = std::str::from_utf8(&a.metadata.module_path).unwrap_or_default();
let params = a.fields;
let metadata : &tracing_core::metadata::Metadata<'static> = (&a.metadata).into();
tracing::span::Span::child_of(
a.parent_id.map(|i|tracing_core::span::Id::from_u64(i)),
&metadata,
&tracing::valueset!{ metadata.fields(), target, name, file, line, module_path, ?params }
)
}
}
impl crate::WasmEntryAttributes {
/// convert the given Attributes to an event and emit it using `tracing_core`.
pub fn emit(self: crate::WasmEntryAttributes) {
let name = std::str::from_utf8(&self.metadata.name).unwrap_or_default();
let target = std::str::from_utf8(&self.metadata.target).unwrap_or_default();
let file = std::str::from_utf8(&self.metadata.file).unwrap_or_default();
let line = self.metadata.line;
let module_path = std::str::from_utf8(&self.metadata.module_path).unwrap_or_default();
let params = self.fields;
let metadata : &tracing_core::metadata::Metadata<'static> = (&self.metadata).into();
tracing_core::Event::child_of(
self.parent_id.map(|i|tracing_core::span::Id::from_u64(i)),
&metadata,
&tracing::valueset!{ metadata.fields(), target, name, file, line, module_path, ?params }
)
}
}
}
#[cfg(feature = "std")]
pub use std_features::*;