Skip to content

Commit bf06460

Browse files
committed
add suffix to builder
1 parent 44d9ee3 commit bf06460

File tree

2 files changed

+144
-45
lines changed

2 files changed

+144
-45
lines changed

tracing-appender/src/rolling.rs

+90-45
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ pub struct RollingWriter<'a>(RwLockReadGuard<'a, File>);
103103
struct Inner {
104104
log_directory: PathBuf,
105105
log_filename_prefix: Option<String>,
106+
log_filename_suffix: Option<String>,
106107
rotation: Rotation,
107108
next_date: AtomicUsize,
108109
}
@@ -170,6 +171,7 @@ impl RollingFileAppender {
170171
/// let file_appender = RollingFileAppender::builder()
171172
/// .rotation(Rotation::HOURLY) // rotate log files once every hour
172173
/// .filename_prefix("myapp") // log file names will be prefixed with `myapp.`
174+
/// .filename_suffix("log") // log file names will be suffixed with `.log`
173175
/// .build("/var/log") // try to build an appender that stores log files in `/var/log`
174176
/// .expect("initializing rolling file appender failed");
175177
/// # drop(file_appender);
@@ -184,11 +186,17 @@ impl RollingFileAppender {
184186
let Builder {
185187
ref rotation,
186188
ref prefix,
189+
ref suffix,
187190
} = builder;
188-
let filename_prefix = prefix.clone();
189191
let directory = directory.as_ref().to_path_buf();
190192
let now = OffsetDateTime::now_utc();
191-
let (state, writer) = Inner::new(now, rotation.clone(), directory, filename_prefix)?;
193+
let (state, writer) = Inner::new(
194+
now,
195+
rotation.clone(),
196+
directory,
197+
prefix.clone(),
198+
suffix.clone(),
199+
)?;
192200
Ok(Self {
193201
state,
194202
writer,
@@ -480,42 +488,38 @@ impl Rotation {
480488
}
481489
}
482490

483-
pub(crate) fn join_date(&self, filename: Option<&str>, date: &OffsetDateTime) -> String {
484-
let date = match *self {
485-
Rotation::MINUTELY => {
486-
let format = format_description::parse("[year]-[month]-[day]-[hour]-[minute]")
487-
.expect("Unable to create a formatter; this is a bug in tracing-appender");
488-
date.format(&format)
489-
.expect("Unable to format OffsetDateTime; this is a bug in tracing-appender")
490-
}
491-
Rotation::HOURLY => {
492-
let format = format_description::parse("[year]-[month]-[day]-[hour]")
493-
.expect("Unable to create a formatter; this is a bug in tracing-appender");
494-
date.format(&format)
495-
.expect("Unable to format OffsetDateTime; this is a bug in tracing-appender")
496-
}
497-
Rotation::DAILY => {
498-
let format = format_description::parse("[year]-[month]-[day]")
499-
.expect("Unable to create a formatter; this is a bug in tracing-appender");
500-
date.format(&format)
501-
.expect("Unable to format OffsetDateTime; this is a bug in tracing-appender")
502-
}
503-
Rotation::NEVER => {
504-
// If there's a name prefix, use that.
505-
if let Some(filename) = filename {
506-
return filename.to_owned();
507-
}
491+
pub(crate) fn join_date(
492+
&self,
493+
filename: Option<&str>,
494+
date: &OffsetDateTime,
495+
suffix: Option<&str>,
496+
) -> String {
497+
let format = self.get_date_format();
498+
let date = date
499+
.format(&format)
500+
.expect("Unable to format OffsetDateTime; this is a bug in tracing-appender");
501+
502+
match (self, filename, suffix) {
503+
(&Rotation::NEVER, Some(filename), None) => filename.to_string(),
504+
(&Rotation::NEVER, Some(filename), Some(suffix)) => format!("{}.{}", filename, suffix),
505+
(&Rotation::NEVER, None, Some(suffix)) => suffix.to_string(),
506+
(_, Some(filename), Some(suffix)) => format!("{}.{}.{}", filename, date, suffix),
507+
(_, Some(filename), None) => format!("{}.{}", filename, date),
508+
(_, None, Some(suffix)) => format!("{}.{}", date, suffix),
509+
(_, None, None) => date,
510+
}
511+
}
508512

509-
// Otherwise, just use the date.
510-
let format = format_description::parse("[year]-[month]-[day]")
511-
.expect("Unable to create a formatter; this is a bug in tracing-appender");
512-
date.format(&format)
513-
.expect("Unable to format OffsetDateTime; this is a bug in tracing-appender")
514-
}
515-
};
516-
match filename {
517-
Some(filename) => format!("{}.{}", filename, date),
518-
None => date,
513+
fn get_date_format<'a>(&self) -> Vec<format_description::FormatItem<'a>> {
514+
match *self {
515+
Rotation::MINUTELY => format_description::parse("[year]-[month]-[day]-[hour]-[minute]")
516+
.expect("Unable to create a formatter; this is a bug in tracing-appender"),
517+
Rotation::HOURLY => format_description::parse("[year]-[month]-[day]-[hour]")
518+
.expect("Unable to create a formatter; this is a bug in tracing-appender"),
519+
Rotation::DAILY => format_description::parse("[year]-[month]-[day]")
520+
.expect("Unable to create a formatter; this is a bug in tracing-appender"),
521+
Rotation::NEVER => format_description::parse("[year]-[month]-[day]")
522+
.expect("Unable to create a formatter; this is a bug in tracing-appender"),
519523
}
520524
}
521525
}
@@ -540,15 +544,21 @@ impl Inner {
540544
rotation: Rotation,
541545
directory: impl AsRef<Path>,
542546
log_filename_prefix: Option<String>,
547+
log_filename_suffix: Option<String>,
543548
) -> Result<(Self, RwLock<File>), builder::InitError> {
544549
let log_directory = directory.as_ref().to_path_buf();
545-
let filename = rotation.join_date(log_filename_prefix.as_deref(), &now);
550+
let filename = rotation.join_date(
551+
log_filename_prefix.as_deref(),
552+
&now,
553+
log_filename_suffix.as_deref(),
554+
);
546555
let next_date = rotation.next_date(&now);
547556
let writer = RwLock::new(create_writer(log_directory.as_ref(), &filename)?);
548557

549558
let inner = Inner {
550559
log_directory,
551560
log_filename_prefix,
561+
log_filename_suffix,
552562
next_date: AtomicUsize::new(
553563
next_date
554564
.map(|date| date.unix_timestamp() as usize)
@@ -560,9 +570,11 @@ impl Inner {
560570
}
561571

562572
fn refresh_writer(&self, now: OffsetDateTime, file: &mut File) {
563-
let filename = self
564-
.rotation
565-
.join_date(self.log_filename_prefix.as_deref(), &now);
573+
let filename = self.rotation.join_date(
574+
self.log_filename_prefix.as_deref(),
575+
&now,
576+
self.log_filename_suffix.as_deref(),
577+
);
566578

567579
match create_writer(&self.log_directory, &filename) {
568580
Ok(new_file) => {
@@ -732,19 +744,51 @@ mod test {
732744
let now = OffsetDateTime::parse("2020-02-01 10:01:00 +00:00:00", &format).unwrap();
733745

734746
// per-minute
735-
let path = Rotation::MINUTELY.join_date(Some("app.log"), &now);
747+
let path = Rotation::MINUTELY.join_date(Some("app.log"), &now, None);
736748
assert_eq!("app.log.2020-02-01-10-01", path);
737749

738750
// per-hour
739-
let path = Rotation::HOURLY.join_date(Some("app.log"), &now);
751+
let path = Rotation::HOURLY.join_date(Some("app.log"), &now, None);
740752
assert_eq!("app.log.2020-02-01-10", path);
741753

742754
// per-day
743-
let path = Rotation::DAILY.join_date(Some("app.log"), &now);
755+
let path = Rotation::DAILY.join_date(Some("app.log"), &now, None);
744756
assert_eq!("app.log.2020-02-01", path);
745757

746758
// never
747-
let path = Rotation::NEVER.join_date(Some("app.log"), &now);
759+
let path = Rotation::NEVER.join_date(Some("app.log"), &now, None);
760+
assert_eq!("app.log", path);
761+
762+
// per-minute with suffix
763+
let path = Rotation::MINUTELY.join_date(Some("app"), &now, Some("log"));
764+
assert_eq!("app.2020-02-01-10-01.log", path);
765+
766+
// per-hour with suffix
767+
let path = Rotation::HOURLY.join_date(Some("app"), &now, Some("log"));
768+
assert_eq!("app.2020-02-01-10.log", path);
769+
770+
// per-day with suffix
771+
let path = Rotation::DAILY.join_date(Some("app"), &now, Some("log"));
772+
assert_eq!("app.2020-02-01.log", path);
773+
774+
// never with suffix
775+
let path = Rotation::NEVER.join_date(Some("app"), &now, Some("log"));
776+
assert_eq!("app.log", path);
777+
778+
// per-minute without prefix
779+
let path = Rotation::MINUTELY.join_date(None, &now, Some("app.log"));
780+
assert_eq!("2020-02-01-10-01.app.log", path);
781+
782+
// per-hour without prefix
783+
let path = Rotation::HOURLY.join_date(None, &now, Some("app.log"));
784+
assert_eq!("2020-02-01-10.app.log", path);
785+
786+
// per-day without prefix
787+
let path = Rotation::DAILY.join_date(None, &now, Some("app.log"));
788+
assert_eq!("2020-02-01.app.log", path);
789+
790+
// never without prefix
791+
let path = Rotation::NEVER.join_date(None, &now, Some("app.log"));
748792
assert_eq!("app.log", path);
749793
}
750794

@@ -766,6 +810,7 @@ mod test {
766810
Rotation::HOURLY,
767811
directory.path(),
768812
Some("test_make_writer".to_string()),
813+
None,
769814
)
770815
.unwrap();
771816

tracing-appender/src/rolling/builder.rs

+54
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use thiserror::Error;
99
pub struct Builder {
1010
pub(super) rotation: Rotation,
1111
pub(super) prefix: Option<String>,
12+
pub(super) suffix: Option<String>,
1213
}
1314

1415
/// Errors returned by [`Builder::build`].
@@ -46,6 +47,7 @@ impl Builder {
4647
Self {
4748
rotation: Rotation::NEVER,
4849
prefix: None,
50+
suffix: None,
4951
}
5052
}
5153

@@ -127,6 +129,58 @@ impl Builder {
127129
Self { prefix, ..self }
128130
}
129131

132+
/// Sets the suffix for log filenames. The suffix is output after the
133+
/// timestamp in the file name, and if it is non-empty, it is preceded by a
134+
/// dot (`.`).
135+
///
136+
/// By default, log files do not have a suffix.
137+
///
138+
/// # Examples
139+
///
140+
/// Setting a suffix:
141+
///
142+
/// ```
143+
/// use tracing_appender::rolling::RollingFileAppender;
144+
///
145+
/// # fn docs() {
146+
/// let appender = RollingFileAppender::builder()
147+
/// .filename_suffix("myapp.log") // log files will have names like "2019-01-01.myapp.log"
148+
/// // ...
149+
/// .build("/var/log")
150+
/// .expect("failed to initialize rolling file appender");
151+
/// # drop(appender)
152+
/// # }
153+
/// ```
154+
///
155+
/// No suffix:
156+
///
157+
/// ```
158+
/// use tracing_appender::rolling::RollingFileAppender;
159+
///
160+
/// # fn docs() {
161+
/// let appender = RollingFileAppender::builder()
162+
/// .filename_suffix("") // log files will have names like "2019-01-01"
163+
/// // ...
164+
/// .build("/var/log")
165+
/// .expect("failed to initialize rolling file appender");
166+
/// # drop(appender)
167+
/// # }
168+
/// ```
169+
///
170+
/// [rotation strategy]: Rotation
171+
#[must_use]
172+
pub fn filename_suffix(self, suffix: impl Into<String>) -> Self {
173+
let suffix = suffix.into();
174+
// If the configured suffix is the empty string, then don't include a
175+
// separator character.
176+
let suffix = if suffix.is_empty() {
177+
None
178+
} else {
179+
Some(suffix)
180+
};
181+
Self { suffix, ..self }
182+
}
183+
130184
/// Builds a new [`RollingFileAppender`] with the configured parameters,
131185
/// emitting log files to the provided directory.
132186
///

0 commit comments

Comments
 (0)