-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathlib.rs
More file actions
303 lines (275 loc) · 11.1 KB
/
Copy pathlib.rs
File metadata and controls
303 lines (275 loc) · 11.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
//! Forward `slog` messages as DTrace USDT probes.
//!
//! Logging is invaluable in production applications. However, it presents a bit of a quandary.
//! Most of the time, only informational or error messages are useful. But when an application
//! crashes or is misbehaving, it can be extremely useful to retrieve more verbose logging
//! information. Unfortunately, this can normally only be accomplished by restarting the process
//! with a new log level.
//!
//! This crate allows applications to attach a [`slog::Drain`], the `Dtrace` drain, to their
//! loggers that forwards all messages to DTrace. This is done with a
//! [`usdt`](https://docs.rs/usdt/latest) probe function, with different probes indicating
//! different log levels.
//!
//! Note that the [`Dtrace`] drain will _only_ send messages to DTrace, but in most situations, one
//! is already sending log messages to some location (stdout, file, syslog, etc.). The
//! [`with_drain`] constructor can be used to generate a [`Dtrace`] drain that will forward
//! messages to an existing drain as well as to DTrace.
//!
//! The DTrace probe that emits log messages is efficient. In particular, when the probe is
//! disabled, it incurs no cost beyond that of any other drain(s) in the hierarchy. However, when
//! the probe is enabled, every message, regardless of log-level, can be viewed in DTrace.
//!
//! Example
//! -------
//!
//! ```bash
//! $ cargo +nightly run --example simple
//!
//! ```
//!
//! You can see that only warning messages are printed in the terminal. However, running a DTrace
//! command in another shell should reveal more messages.
//!
//! ```bash
//! ## dtrace -Z -n 'slog*::: { printf("%s\n", copyinstr(arg0)); }' -q
//! {"ok": {"location":{"module":"simple","file":"examples/simple.rs","line":15},"level":"WARN","timestamp":"2021-10-19T17:55:55.260393409Z","message":"a warning message for everyone","kv":{"cool":true,"key":"value"}}}
//! {"ok": {"location":{"module":"simple","file":"examples/simple.rs","line":16},"level":"INFO","timestamp":"2021-10-19T17:55:55.260531762Z","message":"info is just for dtrace","kv":{"cool":true,"hello":"from dtrace","key":"value"}}}
//! {"ok": {"location":{"module":"simple","file":"examples/simple.rs","line":17},"level":"DEBUG","timestamp":"2021-10-19T17:55:55.260579423Z","message":"only dtrace gets debug messages","kv":{"cool":true,"hello":"from dtrace","key":"value"}}}
//! ```
//!
//! We can see both the warning messages that the example's stdout prints, but also an info and
//! debug message. There are specific probes for each logging level, allowing users to run DTrace
//! actions in response to specific levels of messages. For example, this DTrace command receives
//! just messages emitted via the `debug!` logging macro.
//!
//! ```bash
//! ## dtrace -Z -n 'slog*:::debug { printf("%s\n", copyinstr(arg0)); }' -q
//! {"ok": {"location":{"module":"simple","file":"examples/simple.rs","line":17},"level":"DEBUG","timestamp":"2021-10-19T17:57:30.578681933Z","message":"only dtrace gets debug messages","kv":{"cool":true,"hello":"from dtrace","key":"value"}}}
//! ```
//!
//! Notes
//! -----
//!
//! This crate inherits a reliance on a nightly toolchain from the `usdt` crate.
// Copyright 2022 Oxide Computer Company
//
// 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.
#![cfg_attr(usdt_need_asm, feature(asm))]
#![cfg_attr(all(target_os = "macos", usdt_need_asm_sym), feature(asm_sym))]
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use slog::{Drain, KV};
/// Type alias for a generic JSON map.
pub type JsonMap = serde_json::Map<String, serde_json::Value>;
#[usdt::provider(provider = "slog", probe_format = "{probe}_")]
mod probes {
use crate::Message;
fn trace(msg: &Message) {}
fn debug(msg: &Message) {}
fn info(msg: &Message) {}
fn warn(msg: &Message) {}
fn error(msg: &Message) {}
fn critical(msg: &Message) {}
}
/// `Location` describes the location in the source from which a log message was issued.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
pub struct Location {
/// The Rust module from which the message was issued.
pub module: String,
/// The source file from which the message was issued.
pub file: String,
/// The line of the source file from which the message was issued.
pub line: u32,
}
/// A `Message` captures the all information about a single log message.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
pub struct Message {
/// The information about the [`Location`] of a message in the source.
pub location: Location,
/// The logging level, see [`slog::Level`].
pub level: String,
/// The timestamp at which the message was issued.
///
/// As there may be latencies between a message's emission and consumption in DTrace, this can
/// be useful.
pub timestamp: DateTime<Utc>,
/// The string message emitted in the log entry.
pub message: String,
/// The key-value pairs in this log message, including those of parent loggers.
pub kv: JsonMap,
}
/// `ProbeRegistration` stores the result of registering probes with the DTrace kernel module.
///
/// Though unlikely, it's possible that probe registration fails. This may happen, for example, if
/// the DTrace kernel module is extremely low on memory. One may want to abort the application in
/// this case, or one might decide that a running but degraded application is better than nothing i
/// such a situation. The `ProbeRegistration` enum contains information about whether probes were
/// successfully registered. The caller may decide how to handle such a case.
#[allow(clippy::large_enum_variant)]
#[derive(Debug)]
pub enum ProbeRegistration {
Success,
Failed(String),
}
impl ProbeRegistration {
/// Helper to check if the variant is `Success`.
pub fn is_success(&self) -> bool {
matches!(self, ProbeRegistration::Success)
}
}
/// A [`slog::Drain`] that forwards all log messages to DTrace.
#[derive(Debug)]
pub struct Dtrace<D> {
_phantom: std::marker::PhantomData<D>,
}
impl Dtrace<slog::Discard> {
/// Create a new DTrace logger, emitting messages only to DTrace.
///
/// This method will create a `Dtrace` drain that sends messages _only_ to DTrace. If you wish
/// to emit messages to another location as well, you can use [`with_drain`] or call
/// [`slog::Duplicate`].
///
/// Note that it's possible for probe registration to fail. The result of registering is
/// returned as the second tuple element. It may be inspected so that callers can decide how to
/// handle failure. See [`ProbeRegistration`] for more information.
pub fn new() -> (Self, ProbeRegistration) {
let registration = match usdt::register_probes() {
Ok(_) => ProbeRegistration::Success,
Err(e) => ProbeRegistration::Failed(e.to_string()),
};
(
Self {
_phantom: std::marker::PhantomData,
},
registration,
)
}
}
/// Combine the [`Dtrace`] drain with another drain.
///
/// This duplicates all log messages to `drain` and a new `Dtrace` drain.
///
/// Note that probe registration can fail, see [`ProbeRegistration`] and [`Dtrace::new`] for more
/// information.
pub fn with_drain<D>(drain: D) -> (slog::Duplicate<D, Dtrace<slog::Discard>>, ProbeRegistration)
where
D: Drain,
{
let (d, registration) = Dtrace::new();
(slog::Duplicate(drain, d), registration)
}
// Create a message to emit to DTrace
fn create_dtrace_message(record: &slog::Record, values: &slog::OwnedKVList) -> Message {
let location = Location {
module: record.module().to_string(),
file: record.file().to_string(),
line: record.line(),
};
let mut serializer = Serializer::default();
let kv = match record
.kv()
.serialize(record, &mut serializer)
.and_then(|_| values.serialize(record, &mut serializer))
{
Ok(()) => serializer.map,
Err(e) => {
let mut map = JsonMap::default();
let _ = map.insert(
String::from("err"),
serde_json::Value::from(format!("{}", e)),
);
map
}
};
let msg = Message {
location,
timestamp: Utc::now(),
level: record.level().as_str().to_string(),
message: record.msg().to_string(),
kv,
};
msg
}
impl<D> Drain for Dtrace<D>
where
D: Drain<Ok = (), Err = slog::Never>,
{
type Ok = ();
type Err = slog::Never;
fn log(
&self,
record: &slog::Record<'_>,
values: &slog::OwnedKVList,
) -> Result<Self::Ok, Self::Err> {
match record.level() {
slog::Level::Trace => probes::trace_!(|| create_dtrace_message(record, values)),
slog::Level::Debug => probes::debug_!(|| create_dtrace_message(record, values)),
slog::Level::Info => probes::info_!(|| create_dtrace_message(record, values)),
slog::Level::Warning => probes::warn_!(|| create_dtrace_message(record, values)),
slog::Level::Error => probes::error_!(|| create_dtrace_message(record, values)),
slog::Level::Critical => probes::critical_!(|| create_dtrace_message(record, values)),
}
Ok(())
}
}
// Type used to serialize slog's key-value pairs into JSON.
#[derive(Debug, Clone, Default)]
struct Serializer {
map: crate::JsonMap,
}
impl Serializer {
fn emit<T>(&mut self, key: slog::Key, value: T) -> slog::Result
where
T: Into<serde_json::Value>,
{
self.map.insert(key.to_string(), value.into());
Ok(())
}
}
macro_rules! impl_emit {
($method:ident, $ty:ty) => {
fn $method(&mut self, key: slog::Key, value: $ty) -> slog::Result {
self.emit(key, value).unwrap();
Ok(())
}
};
}
impl slog::Serializer for Serializer {
fn emit_arguments(&mut self, key: slog::Key, values: &std::fmt::Arguments<'_>) -> slog::Result {
self.map
.insert(key.to_string(), format!("{}", values).into());
Ok(())
}
impl_emit!(emit_u8, u8);
impl_emit!(emit_u16, u16);
impl_emit!(emit_u32, u32);
impl_emit!(emit_u64, u64);
impl_emit!(emit_i8, i8);
impl_emit!(emit_i16, i16);
impl_emit!(emit_i32, i32);
impl_emit!(emit_i64, i64);
impl_emit!(emit_isize, isize);
impl_emit!(emit_usize, usize);
impl_emit!(emit_bool, bool);
impl_emit!(emit_f32, f32);
impl_emit!(emit_f64, f64);
impl_emit!(emit_str, &str);
fn emit_unit(&mut self, key: slog::Key) -> slog::Result {
self.emit(key, ())
}
fn emit_none(&mut self, key: slog::Key) -> slog::Result {
self.map.insert(key.to_string(), serde_json::Value::Null);
Ok(())
}
}