Skip to content

Commit e9adef6

Browse files
vibhavpTommyCpp
andauthored
Initial implementation of the logging SDK spec (#788)
* Logging SDK. * Add From implementations for Any. * Fix docs links. * Add Into<Any> impls for f64 and f32. * Support converting serde_json::Value to Any. * Fix docs link. * Remove unused dependency. * Add LogRecordBuilder, documentation for Severity. * Add LogRecordBuilder::new(). * with_body: Remove unneeded generic parameter. * Remove unneeded generic parameters. * Enforce LogProcessor is Send + Sync. * export: Use the correct variables. * LogEmitterProvider: Enable Clone. Add shutdown, try_shutdown. * Remove From implementation for serde_json values. * Fix typo. * Add Default impl for LogRecordBuilder. * Update to work with opentelemetry-proto v0.14.0. * Remove tests. * Avoid using wildcard imports. * Rename feature/module "log" to "logs". * Implement Drop for LogEmitterProvider. * Use std::convert::identity. * Use opentelemetry-log-exporter as the thread name. * Use the correct module name. * Remove From<Severity> impls for SeverityNumber. * log_emitter: Set emitter version as None. * Rename attributes_to_keyvalue to attributes_to_keyv_alue Co-authored-by: Zhongyang Wu <zhongyang.wu@outlook.com> * Update logs * Fix typos in feature names. * Add logs protobuf files to GRPCIO_PROTO_FILES. * Update to opentelemetry-proto 0.19.0. * Update crates/modules names. * Remove incorrect exporter example in docs. * Move stdout logs exporter to the opentelemetry-stdout crate. * Store resource using Cow instead of Arc. * Add From<Cow<str>> implementation for Key. * Update logging SDK. * Add ordered-float dependency. * Rewrite LogsExporter. * Move LogRecord to api crate, simplify resources. * Add API traits for logs. * Add api trait impls. * Add no-op impl for Logger and LoggerProvider. * Use api traits. * Add global logger/loggerproviders. * Add include_trace_context, make the component name param ergonomic. * Update docs. * fix: lint * logs: Rename Any to AnyValue. * Address docs and lint issues. --------- Co-authored-by: Zhongyang Wu <zhongyang.wu@outlook.com>
1 parent 69fd972 commit e9adef6

40 files changed

+4933
-48
lines changed

opentelemetry-api/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,4 @@ default = ["trace"]
2929
trace = ["pin-project-lite"]
3030
metrics = ["fnv"]
3131
testing = ["trace"]
32+
logs = []

opentelemetry-api/src/common.rs

+10
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,16 @@ impl From<Arc<str>> for Key {
9999
}
100100
}
101101

102+
impl From<Cow<'static, str>> for Key {
103+
/// Convert a `Cow<'static, str>` to a `Key`
104+
fn from(string: Cow<'static, str>) -> Self {
105+
match string {
106+
Cow::Borrowed(s) => Key(OtelString::Static(s)),
107+
Cow::Owned(s) => Key(OtelString::Owned(s)),
108+
}
109+
}
110+
}
111+
102112
impl fmt::Debug for Key {
103113
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
104114
self.0.fmt(fmt)

opentelemetry-api/src/global/error_handler.rs

+12
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use std::sync::PoisonError;
22
use std::sync::RwLock;
33

4+
#[cfg(feature = "logs")]
5+
use crate::logs::LogError;
46
#[cfg(feature = "metrics")]
57
use crate::metrics::MetricsError;
68
#[cfg(feature = "trace")]
@@ -23,6 +25,13 @@ pub enum Error {
2325
#[error(transparent)]
2426
/// An issue raised by the metrics module.
2527
Metric(#[from] MetricsError),
28+
29+
#[cfg(feature = "logs")]
30+
#[cfg_attr(docsrs, doc(cfg(feature = "logs")))]
31+
#[error(transparent)]
32+
/// Failed to export logs.
33+
Log(#[from] LogError),
34+
2635
#[error("{0}")]
2736
/// Other types of failures not covered by the variants above.
2837
Other(String),
@@ -49,6 +58,9 @@ pub fn handle_error<T: Into<Error>>(err: T) {
4958
#[cfg(feature = "trace")]
5059
#[cfg_attr(docsrs, doc(cfg(feature = "trace")))]
5160
Error::Trace(err) => eprintln!("OpenTelemetry trace error occurred. {}", err),
61+
#[cfg(feature = "logs")]
62+
#[cfg_attr(docsrs, doc(cfg(feature = "logs")))]
63+
Error::Log(err) => eprintln!("OpenTelemetry log error occurred. {}", err),
5264
Error::Other(err_msg) => eprintln!("OpenTelemetry error occurred. {}", err_msg),
5365
},
5466
}

opentelemetry-api/src/global/logs.rs

+158
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
use std::{
2+
borrow::Cow,
3+
fmt, mem,
4+
sync::{Arc, RwLock},
5+
};
6+
7+
use once_cell::sync::Lazy;
8+
9+
use crate::{
10+
logs::{Logger, LoggerProvider, NoopLoggerProvider},
11+
KeyValue,
12+
};
13+
14+
/// Allows a specific [`LoggerProvider`] to be used generically, by mirroring
15+
/// the interface, and boxing the returned types.
16+
///
17+
/// [`LoggerProvider`]: crate::logs::LoggerProvider.
18+
pub trait ObjectSafeLoggerProvider {
19+
/// Creates a versioned named [`Logger`] instance that is a trait object
20+
/// through the underlying [`LoggerProvider`].
21+
///
22+
/// [`Logger`]: crate::logs::Logger
23+
/// [`LoggerProvider`]: crate::logs::LoggerProvider
24+
fn versioned_logger_boxed(
25+
&self,
26+
name: Cow<'static, str>,
27+
version: Option<Cow<'static, str>>,
28+
schema_url: Option<Cow<'static, str>>,
29+
attributes: Option<Vec<KeyValue>>,
30+
include_trace_context: bool,
31+
) -> Box<dyn Logger + Send + Sync + 'static>;
32+
}
33+
34+
impl<L, P> ObjectSafeLoggerProvider for P
35+
where
36+
L: Logger + Send + Sync + 'static,
37+
P: LoggerProvider<Logger = L>,
38+
{
39+
fn versioned_logger_boxed(
40+
&self,
41+
name: Cow<'static, str>,
42+
version: Option<Cow<'static, str>>,
43+
schema_url: Option<Cow<'static, str>>,
44+
attributes: Option<Vec<KeyValue>>,
45+
include_trace_context: bool,
46+
) -> Box<dyn Logger + Send + Sync + 'static> {
47+
Box::new(self.versioned_logger(
48+
name,
49+
version,
50+
schema_url,
51+
attributes,
52+
include_trace_context,
53+
))
54+
}
55+
}
56+
57+
pub struct BoxedLogger(Box<dyn Logger + Send + Sync + 'static>);
58+
59+
impl fmt::Debug for BoxedLogger {
60+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61+
f.write_str("BoxedLogger")
62+
}
63+
}
64+
65+
impl Logger for BoxedLogger {
66+
fn emit(&self, record: crate::logs::LogRecord) {
67+
self.0.emit(record)
68+
}
69+
}
70+
71+
#[derive(Clone)]
72+
/// Represents the globally configured [`LoggerProvider`] instance.
73+
pub struct GlobalLoggerProvider {
74+
provider: Arc<dyn ObjectSafeLoggerProvider + Send + Sync>,
75+
}
76+
77+
impl fmt::Debug for GlobalLoggerProvider {
78+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79+
f.write_str("GlobalLoggerProvider")
80+
}
81+
}
82+
83+
impl GlobalLoggerProvider {
84+
fn new<
85+
L: Logger + Send + Sync + 'static,
86+
P: LoggerProvider<Logger = L> + Send + Sync + 'static,
87+
>(
88+
provider: P,
89+
) -> Self {
90+
GlobalLoggerProvider {
91+
provider: Arc::new(provider),
92+
}
93+
}
94+
}
95+
96+
impl LoggerProvider for GlobalLoggerProvider {
97+
type Logger = BoxedLogger;
98+
99+
fn versioned_logger(
100+
&self,
101+
name: impl Into<Cow<'static, str>>,
102+
version: Option<Cow<'static, str>>,
103+
schema_url: Option<Cow<'static, str>>,
104+
attributes: Option<Vec<KeyValue>>,
105+
include_trace_context: bool,
106+
) -> Self::Logger {
107+
BoxedLogger(self.provider.versioned_logger_boxed(
108+
name.into(),
109+
version,
110+
schema_url,
111+
attributes,
112+
include_trace_context,
113+
))
114+
}
115+
}
116+
117+
static GLOBAL_LOGGER_PROVIDER: Lazy<RwLock<GlobalLoggerProvider>> =
118+
Lazy::new(|| RwLock::new(GlobalLoggerProvider::new(NoopLoggerProvider::new())));
119+
120+
/// Returns an instance of the currently configured global [`LoggerProvider`]
121+
/// through [`GlobalLoggerProvider`].
122+
///
123+
/// [`LoggerProvider`]: crate::logs::LoggerProvider
124+
pub fn logger_provider() -> GlobalLoggerProvider {
125+
GLOBAL_LOGGER_PROVIDER
126+
.read()
127+
.expect("GLOBAL_LOGGER_PROVIDER RwLock poisoned")
128+
.clone()
129+
}
130+
131+
/// Creates a named instance of [`Logger`] via the configured
132+
/// [`GlobalLoggerProvider`].
133+
///
134+
/// If `name` is an empty string, the provider will use a default name.
135+
///
136+
/// [`Logger`]: crate::logs::Logger
137+
pub fn logger(name: Cow<'static, str>) -> BoxedLogger {
138+
logger_provider().logger(name)
139+
}
140+
141+
/// Sets the given [`LoggerProvider`] instance as the current global provider,
142+
/// returning the [`LoggerProvider`] instance that was previously set as global
143+
/// provider.
144+
pub fn set_logger_provider<L, P>(new_provider: P) -> GlobalLoggerProvider
145+
where
146+
L: Logger + Send + Sync + 'static,
147+
P: LoggerProvider<Logger = L> + Send + Sync + 'static,
148+
{
149+
let mut provider = GLOBAL_LOGGER_PROVIDER
150+
.write()
151+
.expect("GLOBAL_LOGGER_PROVIDER RwLock poisoned");
152+
mem::replace(&mut *provider, GlobalLoggerProvider::new(new_provider))
153+
}
154+
155+
/// Shut down the current global [`LoggerProvider`].
156+
pub fn shutdown_logger_provider() {
157+
let _ = set_logger_provider(NoopLoggerProvider::new());
158+
}

opentelemetry-api/src/global/mod.rs

+8
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,8 @@
142142
//! [`set_meter_provider`]: crate::global::set_meter_provider
143143
144144
mod error_handler;
145+
#[cfg(feature = "logs")]
146+
mod logs;
145147
#[cfg(feature = "metrics")]
146148
mod metrics;
147149
#[cfg(feature = "trace")]
@@ -150,6 +152,12 @@ mod propagation;
150152
mod trace;
151153

152154
pub use error_handler::{handle_error, set_error_handler, Error};
155+
#[cfg(feature = "logs")]
156+
#[cfg_attr(docsrs, doc(cfg(feature = "logs")))]
157+
pub use logs::{
158+
logger, logger_provider, set_logger_provider, shutdown_logger_provider, GlobalLoggerProvider,
159+
ObjectSafeLoggerProvider,
160+
};
153161
#[cfg(feature = "metrics")]
154162
#[cfg_attr(docsrs, doc(cfg(feature = "metrics")))]
155163
pub use metrics::*;

opentelemetry-api/src/lib.rs

+4
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ pub mod propagation;
7171
#[cfg_attr(docsrs, doc(cfg(feature = "trace")))]
7272
pub mod trace;
7373

74+
#[cfg(feature = "logs")]
75+
#[cfg_attr(docsrs, doc(cfg(feature = "logs")))]
76+
pub mod logs;
77+
7478
#[doc(hidden)]
7579
#[cfg(any(feature = "metrics", feature = "trace"))]
7680
pub mod time {

opentelemetry-api/src/logs/logger.rs

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
use std::borrow::Cow;
2+
3+
use crate::{logs::LogRecord, KeyValue};
4+
5+
/// The interface for emitting [`LogRecord`]s.
6+
pub trait Logger {
7+
/// Emit a [`LogRecord`]. If this `Logger` was created with
8+
/// `include_trace_context` set to `true`, the logger will set the record's
9+
/// [`TraceContext`] to the active trace context, using the current thread's
10+
/// [`Context`].
11+
///
12+
/// [`Context`]: crate::Context
13+
/// [`TraceContext`]: crate::logs::TraceContext
14+
fn emit(&self, record: LogRecord);
15+
}
16+
17+
/// Interfaces that can create [`Logger`] instances.
18+
pub trait LoggerProvider {
19+
/// The [`Logger`] type that this provider will return.
20+
type Logger: Logger;
21+
22+
/// Returns a new versioned logger with a given name.
23+
///
24+
/// The `name` should be the application name or the name of the library
25+
/// providing instrumentation. If the name is empty, then an
26+
/// implementation-defined default name may be used instead.
27+
///
28+
/// If `include_trace_context` is `true`, the newly created [`Logger`]
29+
/// should set the [`TraceContext`] associated with a record to the
30+
/// current thread's active trace context, using [`Context`].
31+
///
32+
/// [`Context`]: crate::Context
33+
/// [`TraceContext`]: crate::logs::TraceContext
34+
35+
fn versioned_logger(
36+
&self,
37+
name: impl Into<Cow<'static, str>>,
38+
version: Option<Cow<'static, str>>,
39+
schema_url: Option<Cow<'static, str>>,
40+
attributes: Option<Vec<KeyValue>>,
41+
include_trace_context: bool,
42+
) -> Self::Logger;
43+
44+
/// Returns a new logger with the given name.
45+
///
46+
/// The `name` should be the application name or the name of the library
47+
/// providing instrumentation. If the name is empty, then an
48+
/// implementation-defined default name may be used instead.
49+
fn logger(&self, name: impl Into<Cow<'static, str>>) -> Self::Logger {
50+
self.versioned_logger(name, None, None, None, true)
51+
}
52+
}

opentelemetry-api/src/logs/mod.rs

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
//! # OpenTelemetry Logs API
2+
3+
use crate::ExportError;
4+
use futures_channel::{mpsc::TrySendError, oneshot::Canceled};
5+
use std::time::Duration;
6+
use thiserror::Error;
7+
8+
mod logger;
9+
mod noop;
10+
mod record;
11+
12+
pub use logger::{Logger, LoggerProvider};
13+
pub use noop::NoopLoggerProvider;
14+
pub use record::{AnyValue, LogRecord, LogRecordBuilder, Severity, TraceContext};
15+
16+
/// Describe the result of operations in log SDK.
17+
pub type LogResult<T> = Result<T, LogError>;
18+
19+
#[derive(Error, Debug)]
20+
#[non_exhaustive]
21+
/// Errors returned by the log SDK.
22+
pub enum LogError {
23+
/// Export failed with the error returned by the exporter.
24+
#[error("Exporter {} encountered the following errors: {0}", .0.exporter_name())]
25+
ExportFailed(Box<dyn ExportError>),
26+
27+
/// Export failed to finish after certain period and processor stopped the export.
28+
#[error("Exporter timed out after {} seconds", .0.as_secs())]
29+
ExportTimedOut(Duration),
30+
31+
/// Other errors propagated from log SDK that weren't covered above.
32+
#[error(transparent)]
33+
Other(#[from] Box<dyn std::error::Error + Send + Sync + 'static>),
34+
}
35+
36+
impl<T> From<T> for LogError
37+
where
38+
T: ExportError,
39+
{
40+
fn from(err: T) -> Self {
41+
LogError::ExportFailed(Box::new(err))
42+
}
43+
}
44+
45+
impl<T> From<TrySendError<T>> for LogError {
46+
fn from(err: TrySendError<T>) -> Self {
47+
LogError::Other(Box::new(err.into_send_error()))
48+
}
49+
}
50+
51+
impl From<Canceled> for LogError {
52+
fn from(err: Canceled) -> Self {
53+
LogError::Other(Box::new(err))
54+
}
55+
}
56+
57+
impl From<String> for LogError {
58+
fn from(err_msg: String) -> Self {
59+
LogError::Other(Box::new(Custom(err_msg)))
60+
}
61+
}
62+
63+
impl From<&'static str> for LogError {
64+
fn from(err_msg: &'static str) -> Self {
65+
LogError::Other(Box::new(Custom(err_msg.into())))
66+
}
67+
}
68+
69+
/// Wrap type for string
70+
#[derive(Error, Debug)]
71+
#[error("{0}")]
72+
struct Custom(String);

0 commit comments

Comments
 (0)