-
Notifications
You must be signed in to change notification settings - Fork 26
Expand file tree
/
Copy pathlogger.rs
More file actions
216 lines (193 loc) · 6.81 KB
/
Copy pathlogger.rs
File metadata and controls
216 lines (193 loc) · 6.81 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
/// This target is used exclusively to handle group events.
pub const GROUP_TARGET: &str = "codspeed::group";
pub const OPENED_GROUP_TARGET: &str = "codspeed::group::opened";
pub const ANNOUNCEMENT_TARGET: &str = "codspeed::announcement";
/// Default title used by provider loggers when an announcement is logged
/// without an explicit title.
pub const DEFAULT_ANNOUNCEMENT_TITLE: &str = "New CodSpeed Feature";
/// Internal delimiter (ASCII Unit Separator) used to encode an announcement
/// title alongside its message in a single log record. Reserved control
/// character that is not expected to appear in user-facing strings.
pub const ANNOUNCEMENT_DELIMITER: char = '\x1F';
#[macro_export]
/// Start a new log group. All logs between this and the next `end_group!` will be grouped together.
///
/// # Example
///
/// ```rust
/// # use codspeed_runner::{start_group, end_group};
/// # use log::info;
/// start_group!("My group");
/// info!("This will be grouped");
/// end_group!();
/// ```
macro_rules! start_group {
($name:expr) => {
log::log!(target: $crate::logger::GROUP_TARGET, log::Level::Info, "{}", $name);
};
}
#[macro_export]
/// Start a new opened log group. All logs between this and the next `end_group!` will be grouped together.
///
/// # Example
///
/// ```rust
/// # use codspeed_runner::{start_opened_group, end_group};
/// # use log::info;
/// start_opened_group!("My group");
/// info!("This will be grouped");
/// end_group!();
/// ```
macro_rules! start_opened_group {
($name:expr) => {
log::log!(target: $crate::logger::OPENED_GROUP_TARGET, log::Level::Info, "{}", $name);
};
}
#[macro_export]
/// End the current log group.
/// See [`start_group!`] for more information.
macro_rules! end_group {
() => {
log::log!(target: $crate::logger::GROUP_TARGET, log::Level::Info, "");
};
}
#[macro_export]
/// Logs at the announcement level. This is intended for important announcements like new features,
/// that do not require immediate user action.
///
/// Two forms are supported:
/// - `announcement!("message")`: logs a message with no explicit title; provider loggers fall
/// back to their default presentation (e.g. `"New CodSpeed Feature"` on GitHub Actions).
/// - `announcement!("title", "message")`: logs a message with a custom title; provider loggers
/// surface the title where supported (e.g. as the `title=` field of a GitHub Actions notice).
macro_rules! announcement {
($message:expr) => {
log::log!(target: $crate::logger::ANNOUNCEMENT_TARGET, log::Level::Info, "{}", $message);
};
($title:expr, $message:expr) => {
log::log!(
target: $crate::logger::ANNOUNCEMENT_TARGET,
log::Level::Info,
"{}{}{}",
$title,
$crate::logger::ANNOUNCEMENT_DELIMITER,
$message
);
};
}
pub enum GroupEvent {
Start(String),
StartOpened(String),
End,
}
/// Returns the group event if the record is a group event, otherwise returns `None`.
pub(super) fn get_group_event(record: &log::Record) -> Option<GroupEvent> {
match record.target() {
OPENED_GROUP_TARGET => {
let args = record.args().to_string();
if args.is_empty() {
None
} else {
Some(GroupEvent::StartOpened(args))
}
}
GROUP_TARGET => {
let args = record.args().to_string();
if args.is_empty() {
Some(GroupEvent::End)
} else {
Some(GroupEvent::Start(args))
}
}
_ => None,
}
}
/// A decoded announcement log record.
///
/// Announcements are encoded into a single log record by [`announcement!`], optionally pairing
/// a `title` with the `message` via [`ANNOUNCEMENT_DELIMITER`]. Provider loggers consume this
/// to render announcements in their preferred format.
pub struct AnnouncementEvent {
pub title: Option<String>,
pub message: String,
}
/// Splits an announcement payload into its title and message parts using
/// [`ANNOUNCEMENT_DELIMITER`]. If no delimiter is present, the whole payload is treated as the
/// message and the title is `None`.
fn parse_announcement_args(raw: &str) -> AnnouncementEvent {
if let Some((title, message)) = raw.split_once(ANNOUNCEMENT_DELIMITER) {
AnnouncementEvent {
title: Some(title.to_string()),
message: message.to_string(),
}
} else {
AnnouncementEvent {
title: None,
message: raw.to_string(),
}
}
}
pub(super) fn get_announcement_event(record: &log::Record) -> Option<AnnouncementEvent> {
if record.target() != ANNOUNCEMENT_TARGET {
return None;
}
Some(parse_announcement_args(&record.args().to_string()))
}
#[macro_export]
/// Log a structured JSON output
macro_rules! log_json {
($value:expr) => {
log::log!(target: $crate::logger::JSON_TARGET, log::Level::Info, "{}", $value);
};
}
pub struct JsonEvent(pub String);
pub const JSON_TARGET: &str = "codspeed::json";
pub(super) fn get_json_event(record: &log::Record) -> Option<JsonEvent> {
if record.target() != JSON_TARGET {
return None;
}
Some(JsonEvent(record.args().to_string()))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_announcement_without_title() {
let event = parse_announcement_args("hello");
assert!(event.title.is_none());
assert_eq!(event.message, "hello");
}
#[test]
fn parses_announcement_with_title() {
let raw = format!("OIDC Authentication{ANNOUNCEMENT_DELIMITER}Use OIDC instead of tokens.");
let event = parse_announcement_args(&raw);
assert_eq!(event.title.as_deref(), Some("OIDC Authentication"));
assert_eq!(event.message, "Use OIDC instead of tokens.");
}
#[test]
fn parses_announcement_with_empty_title() {
let raw = format!("{ANNOUNCEMENT_DELIMITER}message-only");
let event = parse_announcement_args(&raw);
assert_eq!(event.title.as_deref(), Some(""));
assert_eq!(event.message, "message-only");
}
#[test]
fn parses_announcement_preserving_multiline_message() {
let raw = format!("Title{ANNOUNCEMENT_DELIMITER}line1\nline2\nline3");
let event = parse_announcement_args(&raw);
assert_eq!(event.title.as_deref(), Some("Title"));
assert_eq!(event.message, "line1\nline2\nline3");
}
#[test]
fn splits_at_first_delimiter_only() {
let raw = format!(
"Title{ANNOUNCEMENT_DELIMITER}message containing the {ANNOUNCEMENT_DELIMITER} char"
);
let event = parse_announcement_args(&raw);
assert_eq!(event.title.as_deref(), Some("Title"));
assert_eq!(
event.message,
format!("message containing the {ANNOUNCEMENT_DELIMITER} char")
);
}
}