mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 17:31:05 +00:00
Telemetry per node (#7463)
This commit is contained in:
@@ -26,12 +26,13 @@
|
||||
//!
|
||||
//! Currently we provide `Log` (default), `Telemetry` variants for `Receiver`
|
||||
|
||||
#![warn(missing_docs)]
|
||||
|
||||
pub mod logging;
|
||||
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::fmt;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use parking_lot::Mutex;
|
||||
use serde::ser::{Serialize, Serializer, SerializeMap};
|
||||
use tracing::{
|
||||
@@ -42,108 +43,17 @@ use tracing::{
|
||||
subscriber::Subscriber,
|
||||
};
|
||||
use tracing_subscriber::{
|
||||
fmt::time::ChronoLocal,
|
||||
CurrentSpan,
|
||||
EnvFilter,
|
||||
layer::{self, Layer, Context},
|
||||
fmt as tracing_fmt,
|
||||
Registry,
|
||||
layer::{Layer, Context},
|
||||
};
|
||||
|
||||
use sc_telemetry::{telemetry, SUBSTRATE_INFO};
|
||||
use sp_tracing::{WASM_NAME_KEY, WASM_TARGET_KEY, WASM_TRACE_IDENTIFIER};
|
||||
use tracing_subscriber::reload::Handle;
|
||||
use once_cell::sync::OnceCell;
|
||||
use tracing_subscriber::filter::Directive;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub use tracing;
|
||||
|
||||
const ZERO_DURATION: Duration = Duration::from_nanos(0);
|
||||
|
||||
// The layered Subscriber as built up in `init_logger()`.
|
||||
// Used in the reload `Handle`.
|
||||
type SCSubscriber<
|
||||
N = tracing_fmt::format::DefaultFields,
|
||||
E = logging::EventFormat<ChronoLocal>,
|
||||
W = fn() -> std::io::Stderr
|
||||
> = layer::Layered<tracing_fmt::Layer<Registry, N, E, W>, Registry>;
|
||||
|
||||
// Handle to reload the tracing log filter
|
||||
static FILTER_RELOAD_HANDLE: OnceCell<Handle<EnvFilter, SCSubscriber>> = OnceCell::new();
|
||||
// Directives that are defaulted to when resetting the log filter
|
||||
static DEFAULT_DIRECTIVES: OnceCell<Mutex<Vec<String>>> = OnceCell::new();
|
||||
// Current state of log filter
|
||||
static CURRENT_DIRECTIVES: OnceCell<Mutex<Vec<String>>> = OnceCell::new();
|
||||
|
||||
/// Initialize FILTER_RELOAD_HANDLE, only possible once
|
||||
pub fn set_reload_handle(handle: Handle<EnvFilter, SCSubscriber>) {
|
||||
let _ = FILTER_RELOAD_HANDLE.set(handle);
|
||||
}
|
||||
|
||||
/// Add log filter directive(s) to the defaults
|
||||
///
|
||||
/// The syntax is identical to the CLI `<target>=<level>`:
|
||||
///
|
||||
/// `sync=debug,state=trace`
|
||||
pub fn add_default_directives(directives: &str) {
|
||||
DEFAULT_DIRECTIVES.get_or_init(|| Mutex::new(Vec::new())).lock().push(directives.to_owned());
|
||||
add_directives(directives);
|
||||
}
|
||||
|
||||
/// Add directives to current directives
|
||||
pub fn add_directives(directives: &str) {
|
||||
CURRENT_DIRECTIVES.get_or_init(|| Mutex::new(Vec::new())).lock().push(directives.to_owned());
|
||||
}
|
||||
|
||||
/// Reload the logging filter with the supplied directives added to the existing directives
|
||||
pub fn reload_filter() -> Result<(), String> {
|
||||
let mut env_filter = EnvFilter::default();
|
||||
if let Some(current_directives) = CURRENT_DIRECTIVES.get() {
|
||||
// Use join and then split in case any directives added together
|
||||
for directive in current_directives.lock().join(",").split(',').map(|d| d.parse()) {
|
||||
match directive {
|
||||
Ok(dir) => env_filter = env_filter.add_directive(dir),
|
||||
Err(invalid_directive) => {
|
||||
log::warn!(
|
||||
target: "tracing",
|
||||
"Unable to parse directive while setting log filter: {:?}",
|
||||
invalid_directive,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
env_filter = env_filter.add_directive(
|
||||
"sc_tracing=trace"
|
||||
.parse()
|
||||
.expect("provided directive is valid"),
|
||||
);
|
||||
log::debug!(target: "tracing", "Reloading log filter with: {}", env_filter);
|
||||
FILTER_RELOAD_HANDLE.get()
|
||||
.ok_or("No reload handle present".to_string())?
|
||||
.reload(env_filter)
|
||||
.map_err(|e| format!("{}", e))
|
||||
}
|
||||
|
||||
/// Resets the log filter back to the original state when the node was started.
|
||||
///
|
||||
/// Includes substrate defaults and CLI supplied directives.
|
||||
pub fn reset_log_filter() -> Result<(), String> {
|
||||
*CURRENT_DIRECTIVES
|
||||
.get_or_init(|| Mutex::new(Vec::new())).lock() =
|
||||
DEFAULT_DIRECTIVES.get_or_init(|| Mutex::new(Vec::new())).lock().clone();
|
||||
reload_filter()
|
||||
}
|
||||
|
||||
/// Parse `Directive` and add to default directives if successful.
|
||||
///
|
||||
/// Ensures the supplied directive will be restored when resetting the log filter.
|
||||
pub fn parse_default_directive(directive: &str) -> Result<Directive, String> {
|
||||
let dir = directive
|
||||
.parse()
|
||||
.map_err(|_| format!("Unable to parse directive: {}", directive))?;
|
||||
add_default_directives(directive);
|
||||
Ok(dir)
|
||||
}
|
||||
|
||||
/// Responsible for assigning ids to new spans, which are not re-used.
|
||||
pub struct ProfilingLayer {
|
||||
targets: Vec<(String, Level)>,
|
||||
@@ -178,10 +88,15 @@ pub trait TraceHandler: Send + Sync {
|
||||
/// Represents a tracing event, complete with values
|
||||
#[derive(Debug)]
|
||||
pub struct TraceEvent {
|
||||
/// Name of the event.
|
||||
pub name: &'static str,
|
||||
/// Target of the event.
|
||||
pub target: String,
|
||||
/// Level of the event.
|
||||
pub level: Level,
|
||||
/// Values for this event.
|
||||
pub values: Values,
|
||||
/// Id of the parent tracing event, if any.
|
||||
pub parent_id: Option<Id>,
|
||||
}
|
||||
|
||||
@@ -291,27 +206,6 @@ impl fmt::Display for Values {
|
||||
}
|
||||
}
|
||||
|
||||
impl slog::SerdeValue for Values {
|
||||
fn as_serde(&self) -> &dyn erased_serde::Serialize {
|
||||
self
|
||||
}
|
||||
|
||||
fn to_sendable(&self) -> Box<dyn slog::SerdeValue + Send + 'static> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl slog::Value for Values {
|
||||
fn serialize(
|
||||
&self,
|
||||
_record: &slog::Record,
|
||||
key: slog::Key,
|
||||
ser: &mut dyn slog::Serializer,
|
||||
) -> slog::Result {
|
||||
ser.emit_serde(key, self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ProfilingLayer {
|
||||
/// Takes a `TracingReceiver` and a comma separated list of targets,
|
||||
/// either with a level: "pallet=trace,frame=debug"
|
||||
@@ -510,7 +404,7 @@ impl TraceHandler for TelemetryTraceHandler {
|
||||
"target" => span_datum.target,
|
||||
"time" => span_datum.overall_time.as_nanos(),
|
||||
"id" => span_datum.id.into_u64(),
|
||||
"parent_id" => span_datum.parent_id.map(|i| i.into_u64()),
|
||||
"parent_id" => span_datum.parent_id.as_ref().map(|i| i.into_u64()),
|
||||
"values" => span_datum.values
|
||||
);
|
||||
}
|
||||
@@ -519,7 +413,7 @@ impl TraceHandler for TelemetryTraceHandler {
|
||||
telemetry!(SUBSTRATE_INFO; "tracing.event";
|
||||
"name" => event.name,
|
||||
"target" => event.target,
|
||||
"parent_id" => event.parent_id.map(|i| i.into_u64()),
|
||||
"parent_id" => event.parent_id.as_ref().map(|i| i.into_u64()),
|
||||
"values" => event.values
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
// Copyright 2021 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/>.
|
||||
|
||||
use once_cell::sync::OnceCell;
|
||||
use parking_lot::Mutex;
|
||||
use tracing_subscriber::{
|
||||
filter::Directive, fmt as tracing_fmt, fmt::time::ChronoLocal, layer, reload::Handle,
|
||||
EnvFilter, Registry,
|
||||
};
|
||||
|
||||
// Handle to reload the tracing log filter
|
||||
static FILTER_RELOAD_HANDLE: OnceCell<Handle<EnvFilter, SCSubscriber>> = OnceCell::new();
|
||||
// Directives that are defaulted to when resetting the log filter
|
||||
static DEFAULT_DIRECTIVES: OnceCell<Mutex<Vec<String>>> = OnceCell::new();
|
||||
// Current state of log filter
|
||||
static CURRENT_DIRECTIVES: OnceCell<Mutex<Vec<String>>> = OnceCell::new();
|
||||
|
||||
/// Add log filter directive(s) to the defaults
|
||||
///
|
||||
/// The syntax is identical to the CLI `<target>=<level>`:
|
||||
///
|
||||
/// `sync=debug,state=trace`
|
||||
pub(crate) fn add_default_directives(directives: &str) {
|
||||
DEFAULT_DIRECTIVES
|
||||
.get_or_init(|| Mutex::new(Vec::new()))
|
||||
.lock()
|
||||
.push(directives.to_owned());
|
||||
add_directives(directives);
|
||||
}
|
||||
|
||||
/// Add directives to current directives
|
||||
pub fn add_directives(directives: &str) {
|
||||
CURRENT_DIRECTIVES
|
||||
.get_or_init(|| Mutex::new(Vec::new()))
|
||||
.lock()
|
||||
.push(directives.to_owned());
|
||||
}
|
||||
|
||||
/// Parse `Directive` and add to default directives if successful.
|
||||
///
|
||||
/// Ensures the supplied directive will be restored when resetting the log filter.
|
||||
pub(crate) fn parse_default_directive(directive: &str) -> super::Result<Directive> {
|
||||
let dir = directive.parse()?;
|
||||
add_default_directives(directive);
|
||||
Ok(dir)
|
||||
}
|
||||
|
||||
/// Reload the logging filter with the supplied directives added to the existing directives
|
||||
pub fn reload_filter() -> Result<(), String> {
|
||||
let mut env_filter = EnvFilter::default();
|
||||
if let Some(current_directives) = CURRENT_DIRECTIVES.get() {
|
||||
// Use join and then split in case any directives added together
|
||||
for directive in current_directives
|
||||
.lock()
|
||||
.join(",")
|
||||
.split(',')
|
||||
.map(|d| d.parse())
|
||||
{
|
||||
match directive {
|
||||
Ok(dir) => env_filter = env_filter.add_directive(dir),
|
||||
Err(invalid_directive) => {
|
||||
log::warn!(
|
||||
target: "tracing",
|
||||
"Unable to parse directive while setting log filter: {:?}",
|
||||
invalid_directive,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
env_filter = env_filter.add_directive(
|
||||
"sc_tracing=trace"
|
||||
.parse()
|
||||
.expect("provided directive is valid"),
|
||||
);
|
||||
log::debug!(target: "tracing", "Reloading log filter with: {}", env_filter);
|
||||
FILTER_RELOAD_HANDLE
|
||||
.get()
|
||||
.ok_or("No reload handle present".to_string())?
|
||||
.reload(env_filter)
|
||||
.map_err(|e| format!("{}", e))
|
||||
}
|
||||
|
||||
/// Resets the log filter back to the original state when the node was started.
|
||||
///
|
||||
/// Includes substrate defaults and CLI supplied directives.
|
||||
pub fn reset_log_filter() -> Result<(), String> {
|
||||
let directive = DEFAULT_DIRECTIVES
|
||||
.get_or_init(|| Mutex::new(Vec::new()))
|
||||
.lock()
|
||||
.clone();
|
||||
|
||||
*CURRENT_DIRECTIVES
|
||||
.get_or_init(|| Mutex::new(Vec::new()))
|
||||
.lock() = directive;
|
||||
reload_filter()
|
||||
}
|
||||
|
||||
/// Initialize FILTER_RELOAD_HANDLE, only possible once
|
||||
pub(crate) fn set_reload_handle(handle: Handle<EnvFilter, SCSubscriber>) {
|
||||
let _ = FILTER_RELOAD_HANDLE.set(handle);
|
||||
}
|
||||
|
||||
// The layered Subscriber as built up in `init_logger()`.
|
||||
// Used in the reload `Handle`.
|
||||
type SCSubscriber<
|
||||
N = tracing_fmt::format::DefaultFields,
|
||||
E = crate::logging::EventFormat<ChronoLocal>,
|
||||
W = fn() -> std::io::Stderr,
|
||||
> = layer::Layered<tracing_fmt::Layer<Registry, N, E, W>, Registry>;
|
||||
+133
-117
@@ -16,92 +16,56 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use std::fmt::{self, Write};
|
||||
use ansi_term::Colour;
|
||||
use tracing::{span::Attributes, Event, Id, Level, Subscriber};
|
||||
use regex::Regex;
|
||||
use std::fmt::{self, Write};
|
||||
use tracing::{Event, Level, Subscriber};
|
||||
use tracing_log::NormalizeEvent;
|
||||
use tracing_subscriber::{
|
||||
field::RecordFields,
|
||||
fmt::{
|
||||
time::{FormatTime, SystemTime},
|
||||
FmtContext, FormatEvent, FormatFields,
|
||||
},
|
||||
layer::Context,
|
||||
registry::LookupSpan,
|
||||
Layer,
|
||||
registry::{LookupSpan, SpanRef},
|
||||
};
|
||||
use regex::Regex;
|
||||
|
||||
/// Span name used for the logging prefix. See macro `sc_cli::prefix_logs_with!`
|
||||
pub const PREFIX_LOG_SPAN: &str = "substrate-log-prefix";
|
||||
|
||||
/// A writer that may write to `inner_writer` with colors.
|
||||
///
|
||||
/// This is used by [`EventFormat`] to kill colors when `enable_color` is `false`.
|
||||
///
|
||||
/// It is required to call [`MaybeColorWriter::write`] after all writes are done,
|
||||
/// because the content of these writes is buffered and will only be written to the
|
||||
/// `inner_writer` at that point.
|
||||
struct MaybeColorWriter<'a> {
|
||||
enable_color: bool,
|
||||
buffer: String,
|
||||
inner_writer: &'a mut dyn fmt::Write,
|
||||
}
|
||||
|
||||
impl<'a> fmt::Write for MaybeColorWriter<'a> {
|
||||
fn write_str(&mut self, buf: &str) -> fmt::Result {
|
||||
self.buffer.push_str(buf);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MaybeColorWriter<'a> {
|
||||
/// Creates a new instance.
|
||||
fn new(enable_color: bool, inner_writer: &'a mut dyn fmt::Write) -> Self {
|
||||
Self {
|
||||
enable_color,
|
||||
inner_writer,
|
||||
buffer: String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Write the buffered content to the `inner_writer`.
|
||||
fn write(&mut self) -> fmt::Result {
|
||||
lazy_static::lazy_static! {
|
||||
static ref RE: Regex = Regex::new("\x1b\\[[^m]+m").expect("Error initializing color regex");
|
||||
}
|
||||
|
||||
if !self.enable_color {
|
||||
let replaced = RE.replace_all(&self.buffer, "");
|
||||
self.inner_writer.write_str(&replaced)
|
||||
} else {
|
||||
self.inner_writer.write_str(&self.buffer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A pre-configured event formatter.
|
||||
pub struct EventFormat<T = SystemTime> {
|
||||
/// Use the given timer for log message timestamps.
|
||||
pub timer: T,
|
||||
/// Sets whether or not an event's target is displayed.
|
||||
pub display_target: bool,
|
||||
/// Sets whether or not an event's level is displayed.
|
||||
pub display_level: bool,
|
||||
/// Sets whether or not the name of the current thread is displayed when formatting events.
|
||||
pub display_thread_name: bool,
|
||||
/// Enable ANSI terminal colors for formatted output.
|
||||
pub enable_color: bool,
|
||||
}
|
||||
|
||||
// NOTE: the following code took inspiration from tracing-subscriber
|
||||
//
|
||||
// https://github.com/tokio-rs/tracing/blob/2f59b32/tracing-subscriber/src/fmt/format/mod.rs#L449
|
||||
impl<S, N, T> FormatEvent<S, N> for EventFormat<T>
|
||||
impl<T> EventFormat<T>
|
||||
where
|
||||
S: Subscriber + for<'a> LookupSpan<'a>,
|
||||
N: for<'a> FormatFields<'a> + 'static,
|
||||
T: FormatTime,
|
||||
{
|
||||
fn format_event(
|
||||
// NOTE: the following code took inspiration from tracing-subscriber
|
||||
//
|
||||
// https://github.com/tokio-rs/tracing/blob/2f59b32/tracing-subscriber/src/fmt/format/mod.rs#L449
|
||||
pub(crate) fn format_event_custom<'b, S, N>(
|
||||
&self,
|
||||
ctx: &FmtContext<S, N>,
|
||||
ctx: CustomFmtContext<'b, S, N>,
|
||||
writer: &mut dyn fmt::Write,
|
||||
event: &Event,
|
||||
) -> fmt::Result {
|
||||
) -> fmt::Result
|
||||
where
|
||||
S: Subscriber + for<'a> LookupSpan<'a>,
|
||||
N: for<'a> FormatFields<'a> + 'static,
|
||||
{
|
||||
if event.metadata().target() == sc_telemetry::TELEMETRY_LOG_SPAN {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let writer = &mut MaybeColorWriter::new(self.enable_color, writer);
|
||||
let normalized_meta = event.normalized_metadata();
|
||||
let meta = normalized_meta.as_ref().unwrap_or_else(|| event.metadata());
|
||||
@@ -134,8 +98,8 @@ where
|
||||
let parents = span.parents();
|
||||
for span in std::iter::once(span).chain(parents) {
|
||||
let exts = span.extensions();
|
||||
if let Some(node_name) = exts.get::<NodeName>() {
|
||||
write!(writer, "{}", node_name.as_str())?;
|
||||
if let Some(prefix) = exts.get::<super::layers::Prefix>() {
|
||||
write!(writer, "{}", prefix.as_str())?;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -148,62 +112,22 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NodeNameLayer;
|
||||
|
||||
impl<S> Layer<S> for NodeNameLayer
|
||||
// NOTE: the following code took inspiration from tracing-subscriber
|
||||
//
|
||||
// https://github.com/tokio-rs/tracing/blob/2f59b32/tracing-subscriber/src/fmt/format/mod.rs#L449
|
||||
impl<S, N, T> FormatEvent<S, N> for EventFormat<T>
|
||||
where
|
||||
S: Subscriber + for<'a> LookupSpan<'a>,
|
||||
N: for<'a> FormatFields<'a> + 'static,
|
||||
T: FormatTime,
|
||||
{
|
||||
fn new_span(&self, attrs: &Attributes<'_>, id: &Id, ctx: Context<'_, S>) {
|
||||
let span = ctx
|
||||
.span(id)
|
||||
.expect("new_span has been called for this span; qed");
|
||||
|
||||
if span.name() != PREFIX_LOG_SPAN {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut extensions = span.extensions_mut();
|
||||
|
||||
if extensions.get_mut::<NodeName>().is_none() {
|
||||
let mut s = String::new();
|
||||
let mut v = NodeNameVisitor(&mut s);
|
||||
attrs.record(&mut v);
|
||||
|
||||
if !s.is_empty() {
|
||||
let fmt_fields = NodeName(s);
|
||||
extensions.insert(fmt_fields);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct NodeNameVisitor<'a, W: std::fmt::Write>(&'a mut W);
|
||||
|
||||
macro_rules! write_node_name {
|
||||
($method:ident, $type:ty, $format:expr) => {
|
||||
fn $method(&mut self, field: &tracing::field::Field, value: $type) {
|
||||
if field.name() == "name" {
|
||||
write!(self.0, $format, value).expect("no way to return the err; qed");
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl<'a, W: std::fmt::Write> tracing::field::Visit for NodeNameVisitor<'a, W> {
|
||||
write_node_name!(record_debug, &dyn std::fmt::Debug, "[{:?}] ");
|
||||
write_node_name!(record_str, &str, "[{}] ");
|
||||
write_node_name!(record_i64, i64, "[{}] ");
|
||||
write_node_name!(record_u64, u64, "[{}] ");
|
||||
write_node_name!(record_bool, bool, "[{}] ");
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct NodeName(String);
|
||||
|
||||
impl NodeName {
|
||||
fn as_str(&self) -> &str {
|
||||
self.0.as_str()
|
||||
fn format_event(
|
||||
&self,
|
||||
ctx: &FmtContext<S, N>,
|
||||
writer: &mut dyn fmt::Write,
|
||||
event: &Event,
|
||||
) -> fmt::Result {
|
||||
self.format_event_custom(CustomFmtContext::FmtContext(ctx), writer, event)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -316,3 +240,95 @@ mod time {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: `FmtContext`'s fields are private. This enum allows us to make a `format_event` function
|
||||
// that works with `FmtContext` or `Context` with `FormatFields`
|
||||
#[allow(dead_code)]
|
||||
pub(crate) enum CustomFmtContext<'a, S, N> {
|
||||
FmtContext(&'a FmtContext<'a, S, N>),
|
||||
ContextWithFormatFields(&'a Context<'a, S>, &'a N),
|
||||
}
|
||||
|
||||
impl<'a, S, N> FormatFields<'a> for CustomFmtContext<'a, S, N>
|
||||
where
|
||||
S: Subscriber + for<'lookup> LookupSpan<'lookup>,
|
||||
N: for<'writer> FormatFields<'writer> + 'static,
|
||||
{
|
||||
fn format_fields<R: RecordFields>(
|
||||
&self,
|
||||
writer: &'a mut dyn fmt::Write,
|
||||
fields: R,
|
||||
) -> fmt::Result {
|
||||
match self {
|
||||
CustomFmtContext::FmtContext(fmt_ctx) => fmt_ctx.format_fields(writer, fields),
|
||||
CustomFmtContext::ContextWithFormatFields(_ctx, fmt_fields) => {
|
||||
fmt_fields.format_fields(writer, fields)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: the following code has been duplicated from tracing-subscriber
|
||||
//
|
||||
// https://github.com/tokio-rs/tracing/blob/2f59b32/tracing-subscriber/src/fmt/fmt_layer.rs#L788
|
||||
impl<'a, S, N> CustomFmtContext<'a, S, N>
|
||||
where
|
||||
S: Subscriber + for<'lookup> LookupSpan<'lookup>,
|
||||
N: for<'writer> FormatFields<'writer> + 'static,
|
||||
{
|
||||
#[inline]
|
||||
pub fn lookup_current(&self) -> Option<SpanRef<'_, S>>
|
||||
where
|
||||
S: for<'lookup> LookupSpan<'lookup>,
|
||||
{
|
||||
match self {
|
||||
CustomFmtContext::FmtContext(fmt_ctx) => fmt_ctx.lookup_current(),
|
||||
CustomFmtContext::ContextWithFormatFields(ctx, _) => ctx.lookup_current(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A writer that may write to `inner_writer` with colors.
|
||||
///
|
||||
/// This is used by [`EventFormat`] to kill colors when `enable_color` is `false`.
|
||||
///
|
||||
/// It is required to call [`MaybeColorWriter::write`] after all writes are done,
|
||||
/// because the content of these writes is buffered and will only be written to the
|
||||
/// `inner_writer` at that point.
|
||||
struct MaybeColorWriter<'a> {
|
||||
enable_color: bool,
|
||||
buffer: String,
|
||||
inner_writer: &'a mut dyn fmt::Write,
|
||||
}
|
||||
|
||||
impl<'a> fmt::Write for MaybeColorWriter<'a> {
|
||||
fn write_str(&mut self, buf: &str) -> fmt::Result {
|
||||
self.buffer.push_str(buf);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MaybeColorWriter<'a> {
|
||||
/// Creates a new instance.
|
||||
fn new(enable_color: bool, inner_writer: &'a mut dyn fmt::Write) -> Self {
|
||||
Self {
|
||||
enable_color,
|
||||
inner_writer,
|
||||
buffer: String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Write the buffered content to the `inner_writer`.
|
||||
fn write(&mut self) -> fmt::Result {
|
||||
lazy_static::lazy_static! {
|
||||
static ref RE: Regex = Regex::new("\x1b\\[[^m]+m").expect("Error initializing color regex");
|
||||
}
|
||||
|
||||
if !self.enable_color {
|
||||
let replaced = RE.replace_all(&self.buffer, "");
|
||||
self.inner_writer.write_str(&replaced)
|
||||
} else {
|
||||
self.inner_writer.write_str(&self.buffer)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2021 Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
|
||||
|
||||
// This program 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.
|
||||
|
||||
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::logging::event_format::{CustomFmtContext, EventFormat};
|
||||
use std::fmt;
|
||||
use tracing::{Event, Level, Subscriber};
|
||||
use tracing_subscriber::{
|
||||
fmt::{
|
||||
time::{FormatTime, SystemTime},
|
||||
FormatFields,
|
||||
},
|
||||
layer::Context,
|
||||
registry::LookupSpan,
|
||||
Layer,
|
||||
};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
/// A `Layer` that display logs in the browser's console.
|
||||
pub struct ConsoleLogLayer<S, N = tracing_subscriber::fmt::format::DefaultFields, T = SystemTime> {
|
||||
event_format: EventFormat<T>,
|
||||
fmt_fields: N,
|
||||
_inner: std::marker::PhantomData<S>,
|
||||
}
|
||||
|
||||
impl<S, T> ConsoleLogLayer<S, tracing_subscriber::fmt::format::DefaultFields, T> {
|
||||
/// Create a new [`ConsoleLogLayer`] using the `EventFormat` provided in argument.
|
||||
pub fn new(event_format: EventFormat<T>) -> Self {
|
||||
Self {
|
||||
event_format,
|
||||
fmt_fields: Default::default(),
|
||||
_inner: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: the following code took inspiration from `EventFormat` (in this file)
|
||||
impl<S, N, T: FormatTime> ConsoleLogLayer<S, N, T>
|
||||
where
|
||||
S: Subscriber + for<'a> LookupSpan<'a>,
|
||||
N: for<'writer> FormatFields<'writer> + 'static,
|
||||
{
|
||||
fn format_event(
|
||||
&self,
|
||||
ctx: &Context<'_, S>,
|
||||
writer: &mut dyn fmt::Write,
|
||||
event: &Event,
|
||||
) -> fmt::Result {
|
||||
self.event_format.format_event_custom(
|
||||
CustomFmtContext::ContextWithFormatFields(ctx, &self.fmt_fields),
|
||||
writer,
|
||||
event,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: the following code took inspiration from tracing-subscriber
|
||||
//
|
||||
// https://github.com/tokio-rs/tracing/blob/2f59b32/tracing-subscriber/src/fmt/fmt_layer.rs#L717
|
||||
impl<S, N, T> Layer<S> for ConsoleLogLayer<S, N, T>
|
||||
where
|
||||
S: Subscriber + for<'a> LookupSpan<'a>,
|
||||
N: for<'writer> FormatFields<'writer> + 'static,
|
||||
T: FormatTime + 'static,
|
||||
{
|
||||
fn on_event(&self, event: &Event<'_>, ctx: Context<'_, S>) {
|
||||
thread_local! {
|
||||
static BUF: std::cell::RefCell<String> = std::cell::RefCell::new(String::new());
|
||||
}
|
||||
|
||||
BUF.with(|buf| {
|
||||
let borrow = buf.try_borrow_mut();
|
||||
let mut a;
|
||||
let mut b;
|
||||
let mut buf = match borrow {
|
||||
Ok(buf) => {
|
||||
a = buf;
|
||||
&mut *a
|
||||
}
|
||||
_ => {
|
||||
b = String::new();
|
||||
&mut b
|
||||
}
|
||||
};
|
||||
|
||||
if self.format_event(&ctx, &mut buf, event).is_ok() {
|
||||
if !buf.is_empty() {
|
||||
let meta = event.metadata();
|
||||
let level = meta.level();
|
||||
// NOTE: the following code took inspiration from tracing-subscriber
|
||||
//
|
||||
// https://github.com/iamcodemaker/console_log/blob/f13b5d6755/src/lib.rs#L149
|
||||
match *level {
|
||||
Level::ERROR => web_sys::console::error_1(&JsValue::from(buf.as_str())),
|
||||
Level::WARN => web_sys::console::warn_1(&JsValue::from(buf.as_str())),
|
||||
Level::INFO => web_sys::console::info_1(&JsValue::from(buf.as_str())),
|
||||
Level::DEBUG => web_sys::console::log_1(&JsValue::from(buf.as_str())),
|
||||
Level::TRACE => web_sys::console::debug_1(&JsValue::from(buf.as_str())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buf.clear();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2021 Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
|
||||
|
||||
// This program 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.
|
||||
|
||||
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#[cfg(target_os = "unknown")]
|
||||
mod console_log;
|
||||
mod prefix_layer;
|
||||
|
||||
#[cfg(target_os = "unknown")]
|
||||
pub use console_log::*;
|
||||
pub use prefix_layer::*;
|
||||
@@ -0,0 +1,86 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2021 Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
|
||||
|
||||
// This program 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.
|
||||
|
||||
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use tracing::{span::Attributes, Id, Subscriber};
|
||||
use tracing_subscriber::{layer::Context, registry::LookupSpan, Layer};
|
||||
|
||||
/// Span name used for the logging prefix. See macro `sc_tracing::logging::prefix_logs_with!`
|
||||
pub const PREFIX_LOG_SPAN: &str = "substrate-log-prefix";
|
||||
|
||||
/// A `Layer` that captures the prefix span ([`PREFIX_LOG_SPAN`]) which is then used by
|
||||
/// [`EventFormat`] to prefix the log lines by customizable string.
|
||||
///
|
||||
/// See the macro `sc_cli::prefix_logs_with!` for more details.
|
||||
pub struct PrefixLayer;
|
||||
|
||||
impl<S> Layer<S> for PrefixLayer
|
||||
where
|
||||
S: Subscriber + for<'a> LookupSpan<'a>,
|
||||
{
|
||||
fn new_span(&self, attrs: &Attributes<'_>, id: &Id, ctx: Context<'_, S>) {
|
||||
let span = ctx
|
||||
.span(id)
|
||||
.expect("new_span has been called for this span; qed");
|
||||
|
||||
if span.name() != PREFIX_LOG_SPAN {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut extensions = span.extensions_mut();
|
||||
|
||||
if extensions.get_mut::<Prefix>().is_none() {
|
||||
let mut s = String::new();
|
||||
let mut v = PrefixVisitor(&mut s);
|
||||
attrs.record(&mut v);
|
||||
|
||||
if !s.is_empty() {
|
||||
let fmt_fields = Prefix(s);
|
||||
extensions.insert(fmt_fields);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PrefixVisitor<'a, W: std::fmt::Write>(&'a mut W);
|
||||
|
||||
macro_rules! write_node_name {
|
||||
($method:ident, $type:ty, $format:expr) => {
|
||||
fn $method(&mut self, field: &tracing::field::Field, value: $type) {
|
||||
if field.name() == "name" {
|
||||
let _ = write!(self.0, $format, value);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl<'a, W: std::fmt::Write> tracing::field::Visit for PrefixVisitor<'a, W> {
|
||||
write_node_name!(record_debug, &dyn std::fmt::Debug, "[{:?}] ");
|
||||
write_node_name!(record_str, &str, "[{}] ");
|
||||
write_node_name!(record_i64, i64, "[{}] ");
|
||||
write_node_name!(record_u64, u64, "[{}] ");
|
||||
write_node_name!(record_bool, bool, "[{}] ");
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Prefix(String);
|
||||
|
||||
impl Prefix {
|
||||
pub(crate) fn as_str(&self) -> &str {
|
||||
self.0.as_str()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,531 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2021 Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
|
||||
|
||||
// This program 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.
|
||||
|
||||
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
//! Substrate logging library.
|
||||
//!
|
||||
//! This crate uses tokio's [tracing](https://github.com/tokio-rs/tracing/) library for logging.
|
||||
|
||||
#![warn(missing_docs)]
|
||||
|
||||
mod directives;
|
||||
mod event_format;
|
||||
mod layers;
|
||||
|
||||
pub use directives::*;
|
||||
pub use sc_tracing_proc_macro::*;
|
||||
|
||||
use sc_telemetry::{ExtTransport, TelemetryWorker};
|
||||
use std::io;
|
||||
use tracing::Subscriber;
|
||||
use tracing_subscriber::{
|
||||
fmt::time::ChronoLocal,
|
||||
fmt::{
|
||||
format, FormatEvent, FormatFields, Formatter, Layer as FmtLayer, MakeWriter,
|
||||
SubscriberBuilder,
|
||||
},
|
||||
layer::{self, SubscriberExt},
|
||||
registry::LookupSpan,
|
||||
EnvFilter, FmtSubscriber, Layer, Registry,
|
||||
};
|
||||
|
||||
pub use event_format::*;
|
||||
pub use layers::*;
|
||||
|
||||
/// Logging Result typedef.
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// Logging errors.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[allow(missing_docs)]
|
||||
#[non_exhaustive]
|
||||
pub enum Error {
|
||||
#[error(transparent)]
|
||||
IoError(#[from] io::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
SetGlobalDefaultError(#[from] tracing::subscriber::SetGlobalDefaultError),
|
||||
|
||||
#[error(transparent)]
|
||||
DirectiveParseError(#[from] tracing_subscriber::filter::ParseError),
|
||||
|
||||
#[error(transparent)]
|
||||
SetLoggerError(#[from] tracing_log::log_tracer::SetLoggerError),
|
||||
}
|
||||
|
||||
macro_rules! disable_log_reloading {
|
||||
($builder:expr) => {{
|
||||
let builder = $builder.with_filter_reloading();
|
||||
let handle = builder.reload_handle();
|
||||
set_reload_handle(handle);
|
||||
builder
|
||||
}};
|
||||
}
|
||||
|
||||
/// Common implementation to get the subscriber.
|
||||
fn get_subscriber_internal<N, E, F, W>(
|
||||
pattern: &str,
|
||||
max_level: Option<log::LevelFilter>,
|
||||
force_colors: Option<bool>,
|
||||
telemetry_buffer_size: Option<usize>,
|
||||
telemetry_external_transport: Option<ExtTransport>,
|
||||
builder_hook: impl Fn(
|
||||
SubscriberBuilder<
|
||||
format::DefaultFields,
|
||||
EventFormat<ChronoLocal>,
|
||||
EnvFilter,
|
||||
fn() -> std::io::Stderr,
|
||||
>,
|
||||
) -> SubscriberBuilder<N, E, F, W>,
|
||||
) -> Result<(impl Subscriber + for<'a> LookupSpan<'a>, TelemetryWorker)>
|
||||
where
|
||||
N: for<'writer> FormatFields<'writer> + 'static,
|
||||
E: FormatEvent<Registry, N> + 'static,
|
||||
W: MakeWriter + 'static,
|
||||
F: layer::Layer<Formatter<N, E, W>> + Send + Sync + 'static,
|
||||
FmtLayer<Registry, N, E, W>: layer::Layer<Registry> + Send + Sync + 'static,
|
||||
{
|
||||
// Accept all valid directives and print invalid ones
|
||||
fn parse_user_directives(mut env_filter: EnvFilter, dirs: &str) -> Result<EnvFilter> {
|
||||
for dir in dirs.split(',') {
|
||||
env_filter = env_filter.add_directive(parse_default_directive(&dir)?);
|
||||
}
|
||||
Ok(env_filter)
|
||||
}
|
||||
|
||||
// Initialize filter - ensure to use `parse_default_directive` for any defaults to persist
|
||||
// after log filter reloading by RPC
|
||||
let mut env_filter = EnvFilter::default()
|
||||
// Enable info
|
||||
.add_directive(parse_default_directive("info").expect("provided directive is valid"))
|
||||
// Disable info logging by default for some modules.
|
||||
.add_directive(parse_default_directive("ws=off").expect("provided directive is valid"))
|
||||
.add_directive(parse_default_directive("yamux=off").expect("provided directive is valid"))
|
||||
.add_directive(
|
||||
parse_default_directive("cranelift_codegen=off").expect("provided directive is valid"),
|
||||
)
|
||||
// Set warn logging by default for some modules.
|
||||
.add_directive(
|
||||
parse_default_directive("cranelift_wasm=warn").expect("provided directive is valid"),
|
||||
)
|
||||
.add_directive(parse_default_directive("hyper=warn").expect("provided directive is valid"));
|
||||
|
||||
if let Ok(lvl) = std::env::var("RUST_LOG") {
|
||||
if lvl != "" {
|
||||
env_filter = parse_user_directives(env_filter, &lvl)?;
|
||||
}
|
||||
}
|
||||
|
||||
if pattern != "" {
|
||||
// We're not sure if log or tracing is available at this moment, so silently ignore the
|
||||
// parse error.
|
||||
env_filter = parse_user_directives(env_filter, pattern)?;
|
||||
}
|
||||
|
||||
let max_level_hint = Layer::<FmtSubscriber>::max_level_hint(&env_filter);
|
||||
|
||||
let max_level = max_level.unwrap_or_else(|| match max_level_hint {
|
||||
Some(tracing_subscriber::filter::LevelFilter::INFO) | None => log::LevelFilter::Info,
|
||||
Some(tracing_subscriber::filter::LevelFilter::TRACE) => log::LevelFilter::Trace,
|
||||
Some(tracing_subscriber::filter::LevelFilter::WARN) => log::LevelFilter::Warn,
|
||||
Some(tracing_subscriber::filter::LevelFilter::ERROR) => log::LevelFilter::Error,
|
||||
Some(tracing_subscriber::filter::LevelFilter::DEBUG) => log::LevelFilter::Debug,
|
||||
Some(tracing_subscriber::filter::LevelFilter::OFF) => log::LevelFilter::Off,
|
||||
});
|
||||
|
||||
tracing_log::LogTracer::builder()
|
||||
.with_max_level(max_level)
|
||||
.init()?;
|
||||
|
||||
// If we're only logging `INFO` entries then we'll use a simplified logging format.
|
||||
let simple = match max_level_hint {
|
||||
Some(level) if level <= tracing_subscriber::filter::LevelFilter::INFO => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
let enable_color = force_colors.unwrap_or_else(|| atty::is(atty::Stream::Stderr));
|
||||
let timer = ChronoLocal::with_format(if simple {
|
||||
"%Y-%m-%d %H:%M:%S".to_string()
|
||||
} else {
|
||||
"%Y-%m-%d %H:%M:%S%.3f".to_string()
|
||||
});
|
||||
|
||||
let (telemetry_layer, telemetry_worker) =
|
||||
sc_telemetry::TelemetryLayer::new(telemetry_buffer_size, telemetry_external_transport)?;
|
||||
let event_format = EventFormat {
|
||||
timer,
|
||||
display_target: !simple,
|
||||
display_level: !simple,
|
||||
display_thread_name: !simple,
|
||||
enable_color,
|
||||
};
|
||||
let builder = FmtSubscriber::builder().with_env_filter(env_filter);
|
||||
|
||||
#[cfg(not(target_os = "unknown"))]
|
||||
let builder = builder.with_writer(std::io::stderr as _);
|
||||
|
||||
#[cfg(target_os = "unknown")]
|
||||
let builder = builder.with_writer(std::io::sink);
|
||||
|
||||
#[cfg(not(target_os = "unknown"))]
|
||||
let builder = builder.event_format(event_format);
|
||||
|
||||
#[cfg(not(target_os = "unknown"))]
|
||||
let builder = builder_hook(builder);
|
||||
|
||||
let subscriber = builder.finish().with(PrefixLayer).with(telemetry_layer);
|
||||
|
||||
#[cfg(target_os = "unknown")]
|
||||
let subscriber = subscriber.with(ConsoleLogLayer::new(event_format));
|
||||
|
||||
Ok((subscriber, telemetry_worker))
|
||||
}
|
||||
|
||||
/// A builder that is used to initialize the global logger.
|
||||
pub struct GlobalLoggerBuilder {
|
||||
pattern: String,
|
||||
profiling: Option<(crate::TracingReceiver, String)>,
|
||||
telemetry_buffer_size: Option<usize>,
|
||||
telemetry_external_transport: Option<ExtTransport>,
|
||||
disable_log_reloading: bool,
|
||||
force_colors: Option<bool>,
|
||||
}
|
||||
|
||||
impl GlobalLoggerBuilder {
|
||||
/// Create a new [`GlobalLoggerBuilder`] which can be used to initialize the global logger.
|
||||
pub fn new<S: Into<String>>(pattern: S) -> Self {
|
||||
Self {
|
||||
pattern: pattern.into(),
|
||||
profiling: None,
|
||||
telemetry_buffer_size: None,
|
||||
telemetry_external_transport: None,
|
||||
disable_log_reloading: false,
|
||||
force_colors: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set up the profiling.
|
||||
pub fn with_profiling<S: Into<String>>(
|
||||
&mut self,
|
||||
tracing_receiver: crate::TracingReceiver,
|
||||
profiling_targets: S,
|
||||
) -> &mut Self {
|
||||
self.profiling = Some((tracing_receiver, profiling_targets.into()));
|
||||
self
|
||||
}
|
||||
|
||||
/// Wether or not to disable log reloading.
|
||||
pub fn with_log_reloading(&mut self, enabled: bool) -> &mut Self {
|
||||
self.disable_log_reloading = !enabled;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set a custom buffer size for the telemetry.
|
||||
pub fn with_telemetry_buffer_size(&mut self, buffer_size: usize) -> &mut Self {
|
||||
self.telemetry_buffer_size = Some(buffer_size);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set a custom network transport (used for the telemetry).
|
||||
pub fn with_transport(&mut self, transport: ExtTransport) -> &mut Self {
|
||||
self.telemetry_external_transport = Some(transport);
|
||||
self
|
||||
}
|
||||
|
||||
/// Force enable/disable colors.
|
||||
pub fn with_colors(&mut self, enable: bool) -> &mut Self {
|
||||
self.force_colors = Some(enable);
|
||||
self
|
||||
}
|
||||
|
||||
/// Initialize the global logger
|
||||
///
|
||||
/// This sets various global logging and tracing instances and thus may only be called once.
|
||||
pub fn init(self) -> Result<TelemetryWorker> {
|
||||
if let Some((tracing_receiver, profiling_targets)) = self.profiling {
|
||||
// If profiling is activated, we require `trace` logging.
|
||||
let max_level = Some(log::LevelFilter::Trace);
|
||||
|
||||
if self.disable_log_reloading {
|
||||
let (subscriber, telemetry_worker) = get_subscriber_internal(
|
||||
&format!("{},{},sc_tracing=trace", self.pattern, profiling_targets),
|
||||
max_level,
|
||||
self.force_colors,
|
||||
self.telemetry_buffer_size,
|
||||
self.telemetry_external_transport,
|
||||
|builder| builder,
|
||||
)?;
|
||||
let profiling = crate::ProfilingLayer::new(tracing_receiver, &profiling_targets);
|
||||
|
||||
tracing::subscriber::set_global_default(subscriber.with(profiling))?;
|
||||
|
||||
Ok(telemetry_worker)
|
||||
} else {
|
||||
let (subscriber, telemetry_worker) = get_subscriber_internal(
|
||||
&format!("{},{},sc_tracing=trace", self.pattern, profiling_targets),
|
||||
max_level,
|
||||
self.force_colors,
|
||||
self.telemetry_buffer_size,
|
||||
self.telemetry_external_transport,
|
||||
|builder| disable_log_reloading!(builder),
|
||||
)?;
|
||||
let profiling = crate::ProfilingLayer::new(tracing_receiver, &profiling_targets);
|
||||
|
||||
tracing::subscriber::set_global_default(subscriber.with(profiling))?;
|
||||
|
||||
Ok(telemetry_worker)
|
||||
}
|
||||
} else {
|
||||
if self.disable_log_reloading {
|
||||
let (subscriber, telemetry_worker) = get_subscriber_internal(
|
||||
&self.pattern,
|
||||
None,
|
||||
self.force_colors,
|
||||
self.telemetry_buffer_size,
|
||||
self.telemetry_external_transport,
|
||||
|builder| builder,
|
||||
)?;
|
||||
|
||||
tracing::subscriber::set_global_default(subscriber)?;
|
||||
|
||||
Ok(telemetry_worker)
|
||||
} else {
|
||||
let (subscriber, telemetry_worker) = get_subscriber_internal(
|
||||
&self.pattern,
|
||||
None,
|
||||
self.force_colors,
|
||||
self.telemetry_buffer_size,
|
||||
self.telemetry_external_transport,
|
||||
|builder| disable_log_reloading!(builder),
|
||||
)?;
|
||||
|
||||
tracing::subscriber::set_global_default(subscriber)?;
|
||||
|
||||
Ok(telemetry_worker)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate as sc_tracing;
|
||||
use std::{env, process::Command};
|
||||
use tracing::{metadata::Kind, subscriber::Interest, Callsite, Level, Metadata};
|
||||
|
||||
const EXPECTED_LOG_MESSAGE: &'static str = "yeah logging works as expected";
|
||||
const EXPECTED_NODE_NAME: &'static str = "THE_NODE";
|
||||
|
||||
fn init_logger(pattern: &str) {
|
||||
let _ = GlobalLoggerBuilder::new(pattern).init().unwrap();
|
||||
}
|
||||
|
||||
fn run_in_process(test_name: &str) {
|
||||
if env::var("RUN_IN_PROCESS").is_err() {
|
||||
let status = Command::new(env::current_exe().unwrap())
|
||||
.arg(test_name)
|
||||
.env("RUN_IN_PROCESS", "true")
|
||||
.status()
|
||||
.unwrap();
|
||||
assert!(status.success(), "process did not ended successfully");
|
||||
std::process::exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_logger_filters() {
|
||||
run_in_process("test_logger_filters");
|
||||
|
||||
let test_pattern = "afg=debug,sync=trace,client=warn,telemetry,something-with-dash=error";
|
||||
init_logger(&test_pattern);
|
||||
|
||||
tracing::dispatcher::get_default(|dispatcher| {
|
||||
let test_filter = |target, level| {
|
||||
struct DummyCallSite;
|
||||
impl Callsite for DummyCallSite {
|
||||
fn set_interest(&self, _: Interest) {}
|
||||
fn metadata(&self) -> &Metadata<'_> {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
|
||||
let metadata = tracing::metadata!(
|
||||
name: "",
|
||||
target: target,
|
||||
level: level,
|
||||
fields: &[],
|
||||
callsite: &DummyCallSite,
|
||||
kind: Kind::SPAN,
|
||||
);
|
||||
|
||||
dispatcher.enabled(&metadata)
|
||||
};
|
||||
|
||||
assert!(test_filter("afg", Level::INFO));
|
||||
assert!(test_filter("afg", Level::DEBUG));
|
||||
assert!(!test_filter("afg", Level::TRACE));
|
||||
|
||||
assert!(test_filter("sync", Level::TRACE));
|
||||
assert!(test_filter("client", Level::WARN));
|
||||
|
||||
assert!(test_filter("telemetry", Level::TRACE));
|
||||
assert!(test_filter("something-with-dash", Level::ERROR));
|
||||
});
|
||||
}
|
||||
|
||||
/// This test ensures that using dash (`-`) in the target name in logs and directives actually
|
||||
/// work.
|
||||
#[test]
|
||||
fn dash_in_target_name_works() {
|
||||
let executable = env::current_exe().unwrap();
|
||||
let output = Command::new(executable)
|
||||
.env("ENABLE_LOGGING", "1")
|
||||
.args(&["--nocapture", "log_something_with_dash_target_name"])
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
let output = String::from_utf8(output.stderr).unwrap();
|
||||
assert!(output.contains(EXPECTED_LOG_MESSAGE));
|
||||
}
|
||||
|
||||
/// This is not an actual test, it is used by the `dash_in_target_name_works` test.
|
||||
/// The given test will call the test executable and only execute this one test that
|
||||
/// only prints `EXPECTED_LOG_MESSAGE` through logging while using a target
|
||||
/// name that contains a dash. This ensures that target names with dashes work.
|
||||
#[test]
|
||||
fn log_something_with_dash_target_name() {
|
||||
if env::var("ENABLE_LOGGING").is_ok() {
|
||||
let test_pattern = "test-target=info";
|
||||
let _guard = init_logger(&test_pattern);
|
||||
|
||||
log::info!(target: "test-target", "{}", EXPECTED_LOG_MESSAGE);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prefix_in_log_lines() {
|
||||
let re = regex::Regex::new(&format!(
|
||||
r"^\d{{4}}-\d{{2}}-\d{{2}} \d{{2}}:\d{{2}}:\d{{2}} \[{}\] {}$",
|
||||
EXPECTED_NODE_NAME, EXPECTED_LOG_MESSAGE,
|
||||
))
|
||||
.unwrap();
|
||||
let executable = env::current_exe().unwrap();
|
||||
let output = Command::new(executable)
|
||||
.env("ENABLE_LOGGING", "1")
|
||||
.args(&["--nocapture", "prefix_in_log_lines_entrypoint"])
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
let output = String::from_utf8(output.stderr).unwrap();
|
||||
assert!(
|
||||
re.is_match(output.trim()),
|
||||
format!("Expected:\n{}\nGot:\n{}", re, output),
|
||||
);
|
||||
}
|
||||
|
||||
/// This is not an actual test, it is used by the `prefix_in_log_lines` test.
|
||||
/// The given test will call the test executable and only execute this one test that
|
||||
/// only prints a log line prefixed by the node name `EXPECTED_NODE_NAME`.
|
||||
#[test]
|
||||
fn prefix_in_log_lines_entrypoint() {
|
||||
if env::var("ENABLE_LOGGING").is_ok() {
|
||||
let _guard = init_logger("");
|
||||
prefix_in_log_lines_process();
|
||||
}
|
||||
}
|
||||
|
||||
#[crate::logging::prefix_logs_with(EXPECTED_NODE_NAME)]
|
||||
fn prefix_in_log_lines_process() {
|
||||
log::info!("{}", EXPECTED_LOG_MESSAGE);
|
||||
}
|
||||
|
||||
/// This is not an actual test, it is used by the `do_not_write_with_colors_on_tty` test.
|
||||
/// The given test will call the test executable and only execute this one test that
|
||||
/// only prints a log line with some colors in it.
|
||||
#[test]
|
||||
fn do_not_write_with_colors_on_tty_entrypoint() {
|
||||
if env::var("ENABLE_LOGGING").is_ok() {
|
||||
let _guard = init_logger("");
|
||||
log::info!("{}", ansi_term::Colour::Yellow.paint(EXPECTED_LOG_MESSAGE));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn do_not_write_with_colors_on_tty() {
|
||||
let re = regex::Regex::new(&format!(
|
||||
r"^\d{{4}}-\d{{2}}-\d{{2}} \d{{2}}:\d{{2}}:\d{{2}} {}$",
|
||||
EXPECTED_LOG_MESSAGE,
|
||||
))
|
||||
.unwrap();
|
||||
let executable = env::current_exe().unwrap();
|
||||
let output = Command::new(executable)
|
||||
.env("ENABLE_LOGGING", "1")
|
||||
.args(&["--nocapture", "do_not_write_with_colors_on_tty_entrypoint"])
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
let output = String::from_utf8(output.stderr).unwrap();
|
||||
assert!(
|
||||
re.is_match(output.trim()),
|
||||
format!("Expected:\n{}\nGot:\n{}", re, output),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn log_max_level_is_set_properly() {
|
||||
fn run_test(rust_log: Option<String>, tracing_targets: Option<String>) -> String {
|
||||
let executable = env::current_exe().unwrap();
|
||||
let mut command = Command::new(executable);
|
||||
|
||||
command
|
||||
.env("PRINT_MAX_LOG_LEVEL", "1")
|
||||
.args(&["--nocapture", "log_max_level_is_set_properly"]);
|
||||
|
||||
if let Some(rust_log) = rust_log {
|
||||
command.env("RUST_LOG", rust_log);
|
||||
}
|
||||
|
||||
if let Some(tracing_targets) = tracing_targets {
|
||||
command.env("TRACING_TARGETS", tracing_targets);
|
||||
}
|
||||
|
||||
let output = command.output().unwrap();
|
||||
|
||||
String::from_utf8(output.stderr).unwrap()
|
||||
}
|
||||
|
||||
if env::var("PRINT_MAX_LOG_LEVEL").is_ok() {
|
||||
init_logger(&env::var("TRACING_TARGETS").unwrap_or_default());
|
||||
eprint!("MAX_LOG_LEVEL={:?}", log::max_level());
|
||||
} else {
|
||||
assert_eq!("MAX_LOG_LEVEL=Info", run_test(None, None));
|
||||
assert_eq!(
|
||||
"MAX_LOG_LEVEL=Trace",
|
||||
run_test(Some("test=trace".into()), None)
|
||||
);
|
||||
assert_eq!(
|
||||
"MAX_LOG_LEVEL=Debug",
|
||||
run_test(Some("test=debug".into()), None)
|
||||
);
|
||||
assert_eq!(
|
||||
"MAX_LOG_LEVEL=Trace",
|
||||
run_test(None, Some("test=info".into()))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user