mirror of
https://github.com/tokio-rs/tokio.git
synced 2025-09-28 12:10:37 +00:00
trace: prepare tokio-trace for release (#1051)
This commit is contained in:
parent
fea1f780bc
commit
3ebca76a9a
3
tokio-trace/CHANGELOG.md
Normal file
3
tokio-trace/CHANGELOG.md
Normal file
@ -0,0 +1,3 @@
|
||||
# 0.1.0 (April 22, 2019)
|
||||
|
||||
- Initial release
|
@ -1,21 +1,26 @@
|
||||
[package]
|
||||
name = "tokio-trace"
|
||||
version = "0.0.1"
|
||||
authors = ["Eliza Weisman <eliza@buoyant.io>"]
|
||||
# When releasing to crates.io:
|
||||
# - Update html_root_url.
|
||||
# - Update doc url
|
||||
# - Cargo.toml
|
||||
# - README.md
|
||||
# - Update CHANGELOG.md.
|
||||
# - Create "v0.1.x" git tag
|
||||
version = "0.1.0"
|
||||
authors = ["Tokio Contributors <team@tokio.rs>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tokio-rs/tokio"
|
||||
homepage = "https://tokio.rs"
|
||||
documentation = "https://docs.rs/tokio-trace/0.1.0/tokio_trace"
|
||||
description = """
|
||||
A scoped, structured logging and diagnostics system.
|
||||
"""
|
||||
categories = ["development-tools::debugging", "asynchronous"]
|
||||
keywords = ["logging", "tracing"]
|
||||
|
||||
# Not yet ready for production.
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
tokio-trace-core = { path = "./tokio-trace-core" }
|
||||
tokio-trace-core = "0.2"
|
||||
log = { version = "0.4", optional = true }
|
||||
cfg-if = "0.1.7"
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
A scoped, structured logging and diagnostics system.
|
||||
|
||||
[Documentation](https://tokio-rs.github.io/tokio/doc/tokio_trace/)
|
||||
[Documentation](https://docs.rs/tokio-trace/0.1.0/tokio_trace/index.html)
|
||||
|
||||
## Overview
|
||||
|
||||
@ -29,7 +29,7 @@ First, add this to your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
tokio-trace = { git = "https://github.com/tokio-rs/tokio" }
|
||||
tokio-trace = "0.1"
|
||||
```
|
||||
|
||||
Next, add this to your crate:
|
||||
|
@ -10,6 +10,8 @@ use Metadata;
|
||||
/// Indexing a field with a string results in an iterative search that performs
|
||||
/// string comparisons. Thus, if possible, once the key for a field is known, it
|
||||
/// should be used whenever possible.
|
||||
///
|
||||
/// [`Field`]: ../struct.Field.html
|
||||
pub trait AsField: ::sealed::Sealed {
|
||||
/// Attempts to convert `&self` into a `Field` with the specified `metadata`.
|
||||
///
|
||||
|
@ -1,15 +1,59 @@
|
||||
//! Trace verbosity level filtering.
|
||||
//!
|
||||
//! # Compile time filters
|
||||
//!
|
||||
//! Trace verbosity levels can be statically disabled at compile time via Cargo
|
||||
//! features, similar to the [`log` crate]. Trace instrumentation at disabled
|
||||
//! levels will be skipped and will not even be present in the resulting binary
|
||||
//! unless the verbosity level is specified dynamically. This level is
|
||||
//! configured separately for release and debug builds. The features are:
|
||||
//!
|
||||
//! * `max_level_off`
|
||||
//! * `max_level_error`
|
||||
//! * `max_level_warn`
|
||||
//! * `max_level_info`
|
||||
//! * `max_level_debug`
|
||||
//! * `max_level_trace`
|
||||
//! * `release_max_level_off`
|
||||
//! * `release_max_level_error`
|
||||
//! * `release_max_level_warn`
|
||||
//! * `release_max_level_info`
|
||||
//! * `release_max_level_debug`
|
||||
//! * `release_max_level_trace`
|
||||
//!
|
||||
//! These features control the value of the `STATIC_MAX_LEVEL` constant. The
|
||||
//! instrumentation macros macros check this value before recording an event or
|
||||
//! constructing a span. By default, no levels are disabled.
|
||||
//!
|
||||
//! For example, a crate can disable trace level instrumentation in debug builds
|
||||
//! and trace, debug, and info level instrumentation in release builds with the
|
||||
//! following configuration:
|
||||
//!
|
||||
//! ```toml
|
||||
//! [dependencies]
|
||||
//! tokio-trace = { version = "0.1", features = ["max_level_debug", "release_max_level_warn"] }
|
||||
//! ```
|
||||
//!
|
||||
//! [`log` crate]: https://docs.rs/log/0.4.6/log/#compile-time-filters
|
||||
use std::cmp::Ordering;
|
||||
use tokio_trace_core::Level;
|
||||
|
||||
/// `LevelFilter` is used to statistically filter the logging messages based on its `Level`.
|
||||
/// Logging messages will be discarded if its `Level` is greater than `LevelFilter`.
|
||||
/// A filter comparable to trace verbosity `Level`.
|
||||
///
|
||||
/// If a `Level` is considered less than a `LevelFilter`, it should be
|
||||
/// considered disabled; if greater than or equal to the `LevelFilter`, that
|
||||
/// level is enabled.
|
||||
///
|
||||
/// Note that this is essentially identical to the `Level` type, but with the
|
||||
/// addition of an `OFF` level that completely disables all trace
|
||||
/// instrumentation.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
|
||||
pub struct LevelFilter(Option<Level>);
|
||||
|
||||
impl LevelFilter {
|
||||
/// The "off" level.
|
||||
///
|
||||
/// Designates that logging should be to turned off.
|
||||
/// Designates that trace instrumentation should be completely disabled.
|
||||
pub const OFF: LevelFilter = LevelFilter(None);
|
||||
/// The "error" level.
|
||||
///
|
||||
@ -51,12 +95,17 @@ impl PartialOrd<LevelFilter> for Level {
|
||||
}
|
||||
}
|
||||
|
||||
/// The statically resolved maximum trace level.
|
||||
/// The statically configured maximum trace level.
|
||||
///
|
||||
/// See the crate level documentation for information on how to configure this.
|
||||
/// See the [module-level documentation] for information on how to configure
|
||||
/// this.
|
||||
///
|
||||
/// This value is checked by the `event` macro. Code that manually calls functions on that value
|
||||
/// should compare the level against this value.
|
||||
/// This value is checked by the `event!` and `span!` macros. Code that
|
||||
/// manually constructs events or spans via the `Event::record` function or
|
||||
/// `Span` constructors should compare the level against this value to
|
||||
/// determine if those spans or events are enabled.
|
||||
///
|
||||
/// [module-level documentation]: ../index.html#compile-time-filters
|
||||
pub const STATIC_MAX_LEVEL: LevelFilter = MAX_LEVEL;
|
||||
|
||||
cfg_if! {
|
||||
|
@ -1,3 +1,6 @@
|
||||
#![doc(html_root_url = "https://docs.rs/tokio-trace/0.1.0")]
|
||||
#![deny(missing_debug_implementations, missing_docs, unreachable_pub)]
|
||||
#![cfg_attr(test, deny(warnings))]
|
||||
//! A scoped, structured logging and diagnostics system.
|
||||
//!
|
||||
//! # Overview
|
||||
@ -32,6 +35,20 @@
|
||||
//! another context. The span in which a thread is currently executing is
|
||||
//! referred to as the _current_ span.
|
||||
//!
|
||||
//! For example:
|
||||
//! ```
|
||||
//! #[macro_use]
|
||||
//! extern crate tokio_trace;
|
||||
//!
|
||||
//! use tokio_trace::Level;
|
||||
//!
|
||||
//! # fn main() {
|
||||
//! span!(Level::TRACE, "my_span").enter(|| {
|
||||
//! // perform some work in the context of `my_span`...
|
||||
//! });
|
||||
//! # }
|
||||
//!```
|
||||
//!
|
||||
//! Spans form a tree structure — unless it is a root span, all spans have a
|
||||
//! _parent_, and may have one or more _children_. When a new span is created,
|
||||
//! the current span becomes the new span's parent. The total execution time of
|
||||
@ -39,10 +56,43 @@
|
||||
//! represented by its children. Thus, a parent span always lasts for at least
|
||||
//! as long as the longest-executing span in its subtree.
|
||||
//!
|
||||
//! ```
|
||||
//! # #[macro_use] extern crate tokio_trace;
|
||||
//! # use tokio_trace::Level;
|
||||
//! # fn main() {
|
||||
//! // this span is considered the "root" of a new trace tree:
|
||||
//! span!(Level::INFO, "root").enter(|| {
|
||||
//! // since we are now inside "root", this span is considered a child
|
||||
//! // of "root":
|
||||
//! span!(Level::DEBUG, "outer_child").enter(|| {
|
||||
//! // this span is a child of "outer_child", which is in turn a
|
||||
//! // child of "root":
|
||||
//! span!(Level::TRACE, "inner_child").enter(|| {
|
||||
//! // and so on...
|
||||
//! });
|
||||
//! });
|
||||
//! });
|
||||
//! # }
|
||||
//!```
|
||||
//!
|
||||
//! In addition, data may be associated with spans. A span may have _fields_ —
|
||||
//! a set of key-value pairs describing the state of the program during that
|
||||
//! span; an optional name, and metadata describing the source code location
|
||||
//! where the span was originally entered.
|
||||
//! ```
|
||||
//! # #[macro_use] extern crate tokio_trace;
|
||||
//! # use tokio_trace::Level;
|
||||
//! # fn main() {
|
||||
//! // construct a new span with three fields:
|
||||
//! // - "foo", with a value of 42,
|
||||
//! // - "bar", with the value "false"
|
||||
//! // - "baz", with no initial value
|
||||
//! let my_span = span!(Level::INFO, "my_span", foo = 42, bar = false, baz);
|
||||
//!
|
||||
//! // record a value for the field "baz" declared above:
|
||||
//! my_span.record("baz", &"hello world");
|
||||
//! # }
|
||||
//!```
|
||||
//!
|
||||
//! ### When to use spans
|
||||
//!
|
||||
@ -95,9 +145,22 @@
|
||||
//! records emitted by unstructured logging code, but unlike a typical log line,
|
||||
//! an `Event` may occur within the context of a `Span`. Like a `Span`, it
|
||||
//! may have fields, and implicitly inherits any of the fields present on its
|
||||
//! parent span, and it may be linked with one or more additional
|
||||
//! spans that are not its parent; in this case, the event is said to _follow
|
||||
//! from_ those spans.
|
||||
//! parent span.
|
||||
//!
|
||||
//! For example:
|
||||
//! ```
|
||||
//! # #[macro_use] extern crate tokio_trace;
|
||||
//! # use tokio_trace::Level;
|
||||
//! # fn main() {
|
||||
//! // records an event outside of any span context:
|
||||
//! event!(Level::INFO, "something happened");
|
||||
//!
|
||||
//! span!(Level::INFO, "my_span").enter(|| {
|
||||
//! // records an event within "my_span".
|
||||
//! event!(Level::DEBUG, "something happened inside my_span");
|
||||
//! });
|
||||
//! # }
|
||||
//!```
|
||||
//!
|
||||
//! Essentially, `Event`s exist to bridge the gap between traditional
|
||||
//! unstructured logging and span-based tracing. Similar to log records, they
|
||||
@ -139,7 +202,7 @@
|
||||
//!
|
||||
//! ```toml
|
||||
//! [dependencies]
|
||||
//! tokio-trace = { git = "https://github.com/tokio-rs/tokio" }
|
||||
//! tokio-trace = "0.1"
|
||||
//! ```
|
||||
//!
|
||||
//! Next, add this to your crate:
|
||||
@ -230,13 +293,13 @@
|
||||
//! You can find examples showing how to use this crate in the examples
|
||||
//! directory.
|
||||
//!
|
||||
//! ### In libraries
|
||||
//! ## In libraries
|
||||
//!
|
||||
//! Libraries should link only to the `tokio-trace` crate, and use the provided
|
||||
//! macros to record whatever information will be useful to downstream
|
||||
//! consumers.
|
||||
//!
|
||||
//! ### In executables
|
||||
//! ## In executables
|
||||
//!
|
||||
//! In order to record trace events, executables have to use a `Subscriber`
|
||||
//! implementation compatible with `tokio-trace`. A `Subscriber` implements a
|
||||
@ -264,6 +327,7 @@
|
||||
//! # fn new() -> Self { FooSubscriber }
|
||||
//! # }
|
||||
//! # fn main() {
|
||||
//!
|
||||
//! let my_subscriber = FooSubscriber::new();
|
||||
//!
|
||||
//! tokio_trace::subscriber::with_default(my_subscriber, || {
|
||||
@ -299,6 +363,21 @@
|
||||
//! trace tree. This is useful when a project using `tokio-trace` have
|
||||
//! dependencies which use `log`.
|
||||
//!
|
||||
//!
|
||||
//! ## Crate Feature Flags
|
||||
//!
|
||||
//! The following crate feature flags are available:
|
||||
//!
|
||||
//! * A set of features controlling the [static verbosity level].
|
||||
//! * `log` causes trace instrumentation points to emit [`log`] records as well
|
||||
//! as trace events. This is inteded for use in libraries whose users may be
|
||||
//! using either `tokio-trace` or `log`.
|
||||
//!
|
||||
//! ```toml
|
||||
//! [dependencies]
|
||||
//! tokio-trace = { version = "0.1", features = ["log"] }
|
||||
//! ```
|
||||
//!
|
||||
//! [`log`]: https://docs.rs/log/0.4.6/log/
|
||||
//! [`Span`]: span/struct.Span
|
||||
//! [`Event`]: struct.Event.html
|
||||
@ -312,6 +391,7 @@
|
||||
//! [`tokio-trace-futures`]: https://github.com/tokio-rs/tokio-trace-nursery/tree/master/tokio-trace-futures
|
||||
//! [`tokio-trace-fmt`]: https://github.com/tokio-rs/tokio-trace-nursery/tree/master/tokio-trace-fmt
|
||||
//! [`tokio-trace-log`]: https://github.com/tokio-rs/tokio-trace-nursery/tree/master/tokio-trace-log
|
||||
//! [static verbosity level]: level_filters/index.html#compile-time-filters
|
||||
#[macro_use]
|
||||
extern crate cfg_if;
|
||||
extern crate tokio_trace_core;
|
||||
|
@ -4,7 +4,7 @@
|
||||
//!
|
||||
//! A thread of execution is said to _enter_ a span when it begins executing,
|
||||
//! and _exit_ the span when it switches to another context. Spans may be
|
||||
//! entered through the [`enter`](`Span::enter`) method, which enters the target span,
|
||||
//! entered through the [`enter`] method, which enters the target span,
|
||||
//! performs a given function (either a closure or a function pointer), exits
|
||||
//! the span, and then returns the result.
|
||||
//!
|
||||
@ -68,11 +68,10 @@
|
||||
//! Because spans may be entered and exited multiple times before they close,
|
||||
//! [`Subscriber`]s have separate trait methods which are called to notify them
|
||||
//! of span exits and when span handles are dropped. When execution exits a
|
||||
//! span, [`exit`](::Subscriber::exit) will always be called with that span's ID
|
||||
//! to notify the subscriber that the span has been exited. When span handles
|
||||
//! are dropped, the [`drop_span`](::Subscriber::drop_span) method is called
|
||||
//! with that span's ID. The subscriber may use this to determine whether or not
|
||||
//! the span will be entered again.
|
||||
//! span, [`exit`] will always be called with that span's ID to notify the
|
||||
//! subscriber that the span has been exited. When span handles are dropped, the
|
||||
//! [`drop_span`] method is called with that span's ID. The subscriber may use
|
||||
//! this to determine whether or not the span will be entered again.
|
||||
//!
|
||||
//! If there is only a single handle with the capacity to exit a span, dropping
|
||||
//! that handle "close" the span, since the capacity to enter it no longer
|
||||
@ -87,34 +86,20 @@
|
||||
//! }); // --> Subscriber::exit(my_span)
|
||||
//!
|
||||
//! // The handle to `my_span` only lives inside of this block; when it is
|
||||
//! // dropped, the subscriber will be informed that `my_span` has closed.
|
||||
//! // dropped, the subscriber will be informed via `drop_span`.
|
||||
//!
|
||||
//! } // --> Subscriber::close(my_span)
|
||||
//! } // --> Subscriber::drop_span(my_span)
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! A span may be explicitly closed by dropping a handle to it, if it is the only
|
||||
//! handle to that span.
|
||||
//! time it is exited. For example:
|
||||
//! ```
|
||||
//! # #[macro_use] extern crate tokio_trace;
|
||||
//! # use tokio_trace::Level;
|
||||
//! # fn main() {
|
||||
//! use tokio_trace::Span;
|
||||
//!
|
||||
//! let my_span = span!(Level::TRACE, "my_span");
|
||||
//! // Drop the handle to the span.
|
||||
//! drop(my_span); // --> Subscriber::drop_span(my_span)
|
||||
//! # }
|
||||
//! ```
|
||||
//! However, if multiple handles exist, the span can still be re-entered even if
|
||||
//! one or more is dropped. For determining when _all_ handles to a span have
|
||||
//! been dropped, `Subscriber`s have a [`clone_span`](::Subscriber::clone_span)
|
||||
//! method, which is called every time a span handle is cloned. Combined with
|
||||
//! `drop_span`, this may be used to track the number of handles to a given span
|
||||
//! — if `drop_span` has been called one more time than the number of calls to
|
||||
//! `clone_span` for a given ID, then no more handles to the span with that ID
|
||||
//! exist. The subscriber may then treat it as closed.
|
||||
//! been dropped, `Subscriber`s have a [`clone_span`] method, which is called
|
||||
//! every time a span handle is cloned. Combined with `drop_span`, this may be
|
||||
//! used to track the number of handles to a given span — if `drop_span` has
|
||||
//! been called one more time than the number of calls to `clone_span` for a
|
||||
//! given ID, then no more handles to the span with that ID exist. The
|
||||
//! subscriber may then treat it as closed.
|
||||
//!
|
||||
//! # Accessing a Span's Attributes
|
||||
//!
|
||||
@ -125,7 +110,12 @@
|
||||
//! to the [`Subscriber`] when the span is created; it may then choose to cache
|
||||
//! the data for future use, record it in some manner, or discard it completely.
|
||||
//!
|
||||
//! [`Subscriber`]: ::Subscriber
|
||||
//! [`clone_span`]: ../subscriber/trait.Subscriber.html#method.clone_span
|
||||
//! [`drop_span`]: ../subscriber/trait.Subscriber.html#method.drop_span
|
||||
//! [`exit`]: ../subscriber/trait.Subscriber.html#tymethod.exit
|
||||
//! [`Subscriber`]: ../subscriber/trait.Subscriber.html
|
||||
//! [`Attributes`]: struct.Attributes.html
|
||||
//! [`enter`]: struct.Span.html#method.enter
|
||||
pub use tokio_trace_core::span::{Attributes, Id, Record};
|
||||
|
||||
use std::{
|
||||
@ -136,6 +126,8 @@ use {dispatcher::Dispatch, field, Metadata};
|
||||
|
||||
/// Trait implemented by types which have a span `Id`.
|
||||
pub trait AsId: ::sealed::Sealed {
|
||||
/// Returns the `Id` of the span that `self` corresponds to, or `None` if
|
||||
/// this corresponds to a disabled span.
|
||||
fn as_id(&self) -> Option<&Id>;
|
||||
}
|
||||
|
||||
@ -188,8 +180,8 @@ struct Entered<'a> {
|
||||
// ===== impl Span =====
|
||||
|
||||
impl Span {
|
||||
/// Constructs a new `Span` with the given [metadata] and set of [field
|
||||
/// values].
|
||||
/// Constructs a new `Span` with the given [metadata] and set of
|
||||
/// [field values].
|
||||
///
|
||||
/// The new span will be constructed by the currently-active [`Subscriber`],
|
||||
/// with the current span as its parent (if one exists).
|
||||
@ -197,10 +189,10 @@ impl Span {
|
||||
/// After the span is constructed, [field values] and/or [`follows_from`]
|
||||
/// annotations may be added to it.
|
||||
///
|
||||
/// [metadata]: ::metadata::Metadata
|
||||
/// [`Subscriber`]: ::subscriber::Subscriber
|
||||
/// [field values]: ::field::ValueSet
|
||||
/// [`follows_from`]: ::span::Span::follows_from
|
||||
/// [metadata]: ../metadata
|
||||
/// [`Subscriber`]: ../subscriber/trait.Subscriber.html
|
||||
/// [field values]: ../field/struct.ValueSet.html
|
||||
/// [`follows_from`]: ../struct.Span.html#method.follows_from
|
||||
#[inline]
|
||||
pub fn new(meta: &'static Metadata<'static>, values: &field::ValueSet) -> Span {
|
||||
let new_span = Attributes::new(meta, values);
|
||||
@ -213,9 +205,9 @@ impl Span {
|
||||
/// After the span is constructed, [field values] and/or [`follows_from`]
|
||||
/// annotations may be added to it.
|
||||
///
|
||||
/// [metadata]: ::metadata::Metadata
|
||||
/// [field values]: ::field::ValueSet
|
||||
/// [`follows_from`]: ::span::Span::follows_from
|
||||
/// [metadata]: ../metadata
|
||||
/// [field values]: ../field/struct.ValueSet.html
|
||||
/// [`follows_from`]: ../struct.Span.html#method.follows_from
|
||||
#[inline]
|
||||
pub fn new_root(meta: &'static Metadata<'static>, values: &field::ValueSet) -> Span {
|
||||
Self::make(meta, Attributes::new_root(meta, values))
|
||||
@ -227,9 +219,9 @@ impl Span {
|
||||
/// After the span is constructed, [field values] and/or [`follows_from`]
|
||||
/// annotations may be added to it.
|
||||
///
|
||||
/// [metadata]: ::metadata::Metadata
|
||||
/// [field values]: ::field::ValueSet
|
||||
/// [`follows_from`]: ::span::Span::follows_from
|
||||
/// [metadata]: ../metadata
|
||||
/// [field values]: ../field/struct.ValueSet.html
|
||||
/// [`follows_from`]: ../struct.Span.html#method.follows_from
|
||||
pub fn child_of<I>(
|
||||
parent: I,
|
||||
meta: &'static Metadata<'static>,
|
||||
@ -278,8 +270,8 @@ impl Span {
|
||||
result
|
||||
}
|
||||
|
||||
/// Returns a [`Field`](::field::Field) for the field with the given `name`, if
|
||||
/// one exists,
|
||||
/// Returns a [`Field`](../field/struct.Field.html) for the field with the
|
||||
/// given `name`, if one exists,
|
||||
pub fn field<Q: ?Sized>(&self, field: &Q) -> Option<field::Field>
|
||||
where
|
||||
Q: field::AsField,
|
||||
@ -288,7 +280,7 @@ impl Span {
|
||||
}
|
||||
|
||||
/// Returns true if this `Span` has a field for the given
|
||||
/// [`Field`](::field::Field) or field name.
|
||||
/// [`Field`](../field/struct.Field.html) or field name.
|
||||
#[inline]
|
||||
pub fn has_field<Q: ?Sized>(&self, field: &Q) -> bool
|
||||
where
|
||||
@ -450,7 +442,7 @@ impl Inner {
|
||||
/// writing custom span handles, but should generally not be called directly
|
||||
/// when entering a span.
|
||||
#[inline]
|
||||
fn enter<'a>(&'a self) -> Entered<'a> {
|
||||
fn enter(&self) -> Entered {
|
||||
self.subscriber.enter(&self.id);
|
||||
Entered { inner: self }
|
||||
}
|
||||
|
@ -8,9 +8,9 @@ pub use tokio_trace_core::subscriber::*;
|
||||
/// executing, new spans or events are dispatched to the subscriber that
|
||||
/// tagged that span, instead.
|
||||
///
|
||||
/// [`Span`]: ::span::Span
|
||||
/// [`Subscriber`]: ::Subscriber
|
||||
/// [`Event`]: ::Event
|
||||
/// [`Span`]: ../span/struct.Span.html
|
||||
/// [`Subscriber`]: ../subscriber/trait.Subscriber.html
|
||||
/// [`Event`]: :../event/struct.Event.html
|
||||
pub fn with_default<T, S>(subscriber: S, f: impl FnOnce() -> T) -> T
|
||||
where
|
||||
S: Subscriber + Send + Sync + 'static,
|
||||
|
Loading…
x
Reference in New Issue
Block a user