#![doc = include_str!("../README.md")]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(docsrs, doc(auto_cfg))]
#![deny(missing_docs)]
use std::fmt::Debug;
use std::fmt::Display;
use std::sync::Arc;
use log::Level;
use log::log;
use opendal_core::raw::*;
use opendal_core::*;
static LOGGING_TARGET: &str = "opendal::services";
#[derive(Clone, Copy, Debug)]
pub struct LoggingLayer<I = DefaultLoggingInterceptor> {
logger: I,
}
impl Default for LoggingLayer {
fn default() -> Self {
Self {
logger: DefaultLoggingInterceptor,
}
}
}
impl LoggingLayer {
pub fn new<I: LoggingInterceptor>(logger: I) -> LoggingLayer<I> {
LoggingLayer { logger }
}
}
impl<I: LoggingInterceptor> Layer for LoggingLayer<I> {
fn apply_service(&self, inner: Servicer) -> Servicer {
Arc::new(self.layer(inner))
}
}
impl<I: LoggingInterceptor> LoggingLayer<I> {
fn layer(&self, inner: Servicer) -> LoggingService<I> {
let info = inner.info();
LoggingService {
inner,
info,
logger: self.logger.clone(),
}
}
}
pub trait LoggingInterceptor: Debug + Clone + Send + Sync + Unpin + 'static {
fn log(
&self,
info: &ServiceInfo,
operation: Operation,
context: &[(&str, &str)],
message: &str,
err: Option<&Error>,
);
}
#[derive(Clone, Copy, Debug, Default)]
pub struct DefaultLoggingInterceptor;
impl LoggingInterceptor for DefaultLoggingInterceptor {
fn log(
&self,
info: &ServiceInfo,
operation: Operation,
context: &[(&str, &str)],
message: &str,
err: Option<&Error>,
) {
if let Some(err) = err {
let lvl = if err.kind() == ErrorKind::Unexpected {
Level::Error
} else {
Level::Warn
};
log!(
target: LOGGING_TARGET,
lvl,
"service={} name={}{}: {operation} {message} {}",
info.scheme(),
info.name(),
LoggingContext(context),
if err.kind() != ErrorKind::Unexpected {
format!("{err}")
} else {
format!("{err:?}")
}
);
}
log!(
target: LOGGING_TARGET,
Level::Debug,
"service={} name={}{}: {operation} {message}",
info.scheme(),
info.name(),
LoggingContext(context),
);
}
}
struct LoggingContext<'a>(&'a [(&'a str, &'a str)]);
impl Display for LoggingContext<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for (k, v) in self.0.iter() {
write!(f, " {k}={v}")?;
}
Ok(())
}
}
#[doc(hidden)]
pub struct LoggingService<I: LoggingInterceptor> {
inner: Servicer,
info: ServiceInfo,
logger: I,
}
impl<I: LoggingInterceptor> Debug for LoggingService<I> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("LoggingService")
.field("inner", &self.inner)
.field("info", &self.info)
.finish_non_exhaustive()
}
}
impl<I: LoggingInterceptor> LoggingService<I> {
fn log_start(&self, op: Operation, context: &[(&str, &str)]) {
self.logger.log(&self.info, op, context, "started", None);
}
fn log_finish(&self, op: Operation, context: &[(&str, &str)], err: Option<&Error>) {
let message = if err.is_some() { "failed" } else { "finished" };
self.logger.log(&self.info, op, context, message, err);
}
}
impl<I: LoggingInterceptor> Service for LoggingService<I> {
type Reader = LoggingReader<oio::Reader, I>;
type Writer = LoggingWriter<oio::Writer, I>;
type Lister = LoggingLister<oio::Lister, I>;
type Deleter = LoggingDeleter<oio::Deleter, I>;
type Copier = LoggingCopier<oio::Copier, I>;
fn info(&self) -> ServiceInfo {
self.info.clone()
}
fn capability(&self) -> Capability {
self.inner.capability()
}
async fn create_dir(
&self,
ctx: &OperationContext,
path: &str,
args: OpCreateDir,
) -> Result<RpCreateDir> {
self.log_start(Operation::CreateDir, &[("path", path)]);
let result = self.inner.create_dir(ctx, path, args).await;
self.log_finish(
Operation::CreateDir,
&[("path", path)],
result.as_ref().err(),
);
result
}
fn read(&self, ctx: &OperationContext, path: &str, args: OpRead) -> Result<Self::Reader> {
self.log_start(Operation::Read, &[("path", path)]);
self.inner
.read(ctx, path, args)
.map(|r| {
self.logger.log(
&self.info,
Operation::Read,
&[("path", path)],
"created reader",
None,
);
LoggingReader::new(self.info.clone(), self.logger.clone(), path, r)
})
.inspect_err(|err| {
self.logger.log(
&self.info,
Operation::Read,
&[("path", path)],
"failed",
Some(err),
);
})
}
fn write(&self, ctx: &OperationContext, path: &str, args: OpWrite) -> Result<Self::Writer> {
self.log_start(Operation::Write, &[("path", path)]);
self.inner
.write(ctx, path, args)
.map(|w| {
self.logger.log(
&self.info,
Operation::Write,
&[("path", path)],
"created writer",
None,
);
LoggingWriter::new(self.info.clone(), self.logger.clone(), path, w)
})
.inspect_err(|err| {
self.logger.log(
&self.info,
Operation::Write,
&[("path", path)],
"failed",
Some(err),
);
})
}
async fn stat(&self, ctx: &OperationContext, path: &str, args: OpStat) -> Result<RpStat> {
self.log_start(Operation::Stat, &[("path", path)]);
let result = self.inner.stat(ctx, path, args).await;
self.log_finish(Operation::Stat, &[("path", path)], result.as_ref().err());
result
}
fn delete(&self, ctx: &OperationContext) -> Result<Self::Deleter> {
self.log_start(Operation::Delete, &[]);
self.inner
.delete(ctx)
.map(|d| {
self.logger
.log(&self.info, Operation::Delete, &[], "finished", None);
LoggingDeleter::new(self.info.clone(), self.logger.clone(), d)
})
.inspect_err(|err| {
self.logger
.log(&self.info, Operation::Delete, &[], "failed", Some(err));
})
}
fn copy(
&self,
ctx: &OperationContext,
from: &str,
to: &str,
args: OpCopy,
opts: OpCopier,
) -> Result<Self::Copier> {
self.log_start(Operation::Copy, &[("from", from), ("to", to)]);
self.inner
.copy(ctx, from, to, args, opts)
.map(|c| {
self.logger.log(
&self.info,
Operation::Copy,
&[("from", from), ("to", to)],
"created copier",
None,
);
LoggingCopier::new(self.info.clone(), self.logger.clone(), from, to, c)
})
.inspect_err(|err| {
self.logger.log(
&self.info,
Operation::Copy,
&[("from", from), ("to", to)],
"failed",
Some(err),
);
})
}
async fn rename(
&self,
ctx: &OperationContext,
from: &str,
to: &str,
args: OpRename,
) -> Result<RpRename> {
self.log_start(Operation::Rename, &[("from", from), ("to", to)]);
let result = self.inner.rename(ctx, from, to, args).await;
self.log_finish(
Operation::Rename,
&[("from", from), ("to", to)],
result.as_ref().err(),
);
result
}
fn list(&self, ctx: &OperationContext, path: &str, args: OpList) -> Result<Self::Lister> {
self.log_start(Operation::List, &[("path", path)]);
self.inner
.list(ctx, path, args)
.map(|v| {
self.logger.log(
&self.info,
Operation::List,
&[("path", path)],
"created lister",
None,
);
LoggingLister::new(self.info.clone(), self.logger.clone(), path, v)
})
.inspect_err(|err| {
self.logger.log(
&self.info,
Operation::List,
&[("path", path)],
"failed",
Some(err),
);
})
}
async fn presign(
&self,
ctx: &OperationContext,
path: &str,
args: OpPresign,
) -> Result<RpPresign> {
self.log_start(Operation::Presign, &[("path", path)]);
let result = self.inner.presign(ctx, path, args).await;
self.log_finish(Operation::Presign, &[("path", path)], result.as_ref().err());
result
}
}
#[doc(hidden)]
pub struct LoggingReader<R, I: LoggingInterceptor> {
info: ServiceInfo,
logger: I,
path: String,
range: Option<BytesRange>,
read: u64,
inner: R,
}
impl<R, I: LoggingInterceptor> LoggingReader<R, I> {
fn new(info: ServiceInfo, logger: I, path: &str, reader: R) -> Self {
Self::with_range(info, logger, path, None, reader)
}
fn with_range(
info: ServiceInfo,
logger: I,
path: &str,
range: Option<BytesRange>,
reader: R,
) -> Self {
Self {
info,
logger,
path: path.to_string(),
range,
read: 0,
inner: reader,
}
}
fn range_label(&self) -> String {
self.range
.map(|range| range.to_string())
.unwrap_or_default()
}
}
impl<R: oio::ReadStream, I: LoggingInterceptor> oio::ReadStream for LoggingReader<R, I> {
async fn read(&mut self) -> Result<Buffer> {
match self.inner.read().await {
Ok(bs) if bs.is_empty() => {
let range = self.range_label();
self.logger.log(
&self.info,
Operation::Read,
&[
("path", &self.path),
("range", &range),
("read", &self.read.to_string()),
("size", &bs.len().to_string()),
],
"finished",
None,
);
Ok(bs)
}
Ok(bs) => {
self.read += bs.len() as u64;
Ok(bs)
}
Err(err) => {
let range = self.range_label();
self.logger.log(
&self.info,
Operation::Read,
&[
("path", &self.path),
("range", &range),
("read", &self.read.to_string()),
],
"failed",
Some(&err),
);
Err(err)
}
}
}
}
impl<R: oio::Read, I: LoggingInterceptor> oio::Read for LoggingReader<R, I> {
async fn open(&self, range: BytesRange) -> Result<(RpRead, Box<dyn oio::ReadStreamDyn>)> {
match self.inner.open(range).await {
Ok((rp, stream)) => Ok((
rp,
Box::new(LoggingReader::with_range(
self.info.clone(),
self.logger.clone(),
&self.path,
Some(range),
stream,
)) as Box<dyn oio::ReadStreamDyn>,
)),
Err(err) => {
self.logger.log(
&self.info,
Operation::Read,
&[("path", &self.path), ("range", &range.to_string())],
"failed",
Some(&err),
);
Err(err)
}
}
}
async fn read(&self, range: BytesRange) -> Result<(RpRead, Buffer)> {
match self.inner.read(range).await {
Ok((rp, buffer)) => {
self.logger.log(
&self.info,
Operation::Read,
&[
("path", &self.path),
("range", &range.to_string()),
("size", &buffer.len().to_string()),
],
"finished",
None,
);
Ok((rp, buffer))
}
Err(err) => {
self.logger.log(
&self.info,
Operation::Read,
&[("path", &self.path), ("range", &range.to_string())],
"failed",
Some(&err),
);
Err(err)
}
}
}
}
#[doc(hidden)]
pub struct LoggingWriter<W, I> {
info: ServiceInfo,
logger: I,
path: String,
written: u64,
inner: W,
}
impl<W, I> LoggingWriter<W, I> {
fn new(info: ServiceInfo, logger: I, path: &str, writer: W) -> Self {
Self {
info,
logger,
path: path.to_string(),
written: 0,
inner: writer,
}
}
}
impl<W: oio::Write, I: LoggingInterceptor> oio::Write for LoggingWriter<W, I> {
async fn write(&mut self, bs: Buffer) -> Result<()> {
let size = bs.len();
match self.inner.write(bs).await {
Ok(_) => {
self.written += size as u64;
Ok(())
}
Err(err) => {
self.logger.log(
&self.info,
Operation::Write,
&[
("path", &self.path),
("written", &self.written.to_string()),
("size", &size.to_string()),
],
"failed",
Some(&err),
);
Err(err)
}
}
}
async fn abort(&mut self) -> Result<()> {
match self.inner.abort().await {
Ok(_) => {
self.logger.log(
&self.info,
Operation::Write,
&[("path", &self.path), ("written", &self.written.to_string())],
"abort succeeded",
None,
);
Ok(())
}
Err(err) => {
self.logger.log(
&self.info,
Operation::Write,
&[("path", &self.path), ("written", &self.written.to_string())],
"abort failed",
Some(&err),
);
Err(err)
}
}
}
async fn close(&mut self) -> Result<Metadata> {
match self.inner.close().await {
Ok(meta) => {
self.logger.log(
&self.info,
Operation::Write,
&[("path", &self.path), ("written", &self.written.to_string())],
"close succeeded",
None,
);
Ok(meta)
}
Err(err) => {
self.logger.log(
&self.info,
Operation::Write,
&[("path", &self.path), ("written", &self.written.to_string())],
"close failed",
Some(&err),
);
Err(err)
}
}
}
}
#[doc(hidden)]
pub struct LoggingLister<P, I: LoggingInterceptor> {
info: ServiceInfo,
logger: I,
path: String,
listed: usize,
inner: P,
}
impl<P, I: LoggingInterceptor> LoggingLister<P, I> {
fn new(info: ServiceInfo, logger: I, path: &str, inner: P) -> Self {
Self {
info,
logger,
path: path.to_string(),
listed: 0,
inner,
}
}
}
impl<P: oio::List, I: LoggingInterceptor> oio::List for LoggingLister<P, I> {
async fn next(&mut self) -> Result<Option<oio::Entry>> {
let res = self.inner.next().await;
match &res {
Ok(Some(_)) => {
self.listed += 1;
}
Ok(None) => {
self.logger.log(
&self.info,
Operation::List,
&[("path", &self.path), ("listed", &self.listed.to_string())],
"finished",
None,
);
}
Err(err) => {
self.logger.log(
&self.info,
Operation::List,
&[("path", &self.path), ("listed", &self.listed.to_string())],
"failed",
Some(err),
);
}
};
res
}
}
#[doc(hidden)]
pub struct LoggingDeleter<D, I: LoggingInterceptor> {
info: ServiceInfo,
logger: I,
deleted: usize,
inner: D,
}
impl<D, I: LoggingInterceptor> LoggingDeleter<D, I> {
fn new(info: ServiceInfo, logger: I, inner: D) -> Self {
Self {
info,
logger,
deleted: 0,
inner,
}
}
}
impl<D: oio::Delete, I: LoggingInterceptor> oio::Delete for LoggingDeleter<D, I> {
async fn delete(&mut self, path: &str, args: OpDelete) -> Result<()> {
let version = args
.version()
.map(|v| v.to_string())
.unwrap_or_else(|| "<latest>".to_string());
let res = self.inner.delete(path, args).await;
match &res {
Ok(_) => {
self.deleted += 1;
}
Err(err) => {
self.logger.log(
&self.info,
Operation::Delete,
&[
("path", path),
("version", &version),
("deleted", &self.deleted.to_string()),
],
"failed",
Some(err),
);
}
};
res
}
async fn close(&mut self) -> Result<()> {
let res = self.inner.close().await;
match &res {
Ok(_) => {
self.logger.log(
&self.info,
Operation::Delete,
&[("deleted", &self.deleted.to_string())],
"succeeded",
None,
);
}
Err(err) => {
self.logger.log(
&self.info,
Operation::Delete,
&[("deleted", &self.deleted.to_string())],
"failed",
Some(err),
);
}
};
res
}
}
#[doc(hidden)]
pub struct LoggingCopier<C, I: LoggingInterceptor> {
info: ServiceInfo,
logger: I,
from: String,
to: String,
copied: u64,
inner: C,
}
impl<C, I: LoggingInterceptor> LoggingCopier<C, I> {
fn new(info: ServiceInfo, logger: I, from: &str, to: &str, inner: C) -> Self {
Self {
info,
logger,
from: from.to_string(),
to: to.to_string(),
copied: 0,
inner,
}
}
}
impl<C: oio::Copy, I: LoggingInterceptor> oio::Copy for LoggingCopier<C, I> {
async fn next(&mut self) -> Result<Option<usize>> {
match self.inner.next().await {
Ok(Some(n)) => {
self.copied += n as u64;
Ok(Some(n))
}
Ok(None) => {
self.logger.log(
&self.info,
Operation::Copy,
&[
("from", &self.from),
("to", &self.to),
("copied", &self.copied.to_string()),
],
"finished",
None,
);
Ok(None)
}
Err(err) => {
self.logger.log(
&self.info,
Operation::Copy,
&[
("from", &self.from),
("to", &self.to),
("copied", &self.copied.to_string()),
],
"failed",
Some(&err),
);
Err(err)
}
}
}
async fn close(&mut self) -> Result<Metadata> {
self.inner.close().await
}
async fn abort(&mut self) -> Result<()> {
match self.inner.abort().await {
Ok(_) => {
self.logger.log(
&self.info,
Operation::Copy,
&[
("from", &self.from),
("to", &self.to),
("copied", &self.copied.to_string()),
],
"abort succeeded",
None,
);
Ok(())
}
Err(err) => {
self.logger.log(
&self.info,
Operation::Copy,
&[
("from", &self.from),
("to", &self.to),
("copied", &self.copied.to_string()),
],
"abort failed",
Some(&err),
);
Err(err)
}
}
}
}