virtual_fs/
trace_fs.rs

1use std::{
2    path::{Path, PathBuf},
3    pin::Pin,
4    task::{Context, Poll},
5};
6
7use futures::future::BoxFuture;
8use tokio::io::{AsyncRead, AsyncSeek, AsyncWrite, ReadBuf};
9
10use crate::{FileOpener, FileSystem, OpenOptionsConfig, VirtualFile};
11
12/// A [`FileSystem`] wrapper that will automatically log all operations at the
13/// `trace` level.
14///
15/// To see these logs, you will typically need to set the `$RUST_LOG`
16/// environment variable to `virtual_fs::trace_fs=trace`.
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct TraceFileSystem<F>(pub F);
19
20impl<F> TraceFileSystem<F> {
21    pub fn new(filesystem: F) -> Self {
22        TraceFileSystem(filesystem)
23    }
24
25    pub fn inner(&self) -> &F {
26        &self.0
27    }
28
29    pub fn inner_mut(&mut self) -> &mut F {
30        &mut self.0
31    }
32
33    pub fn into_inner(self) -> F {
34        self.0
35    }
36}
37
38impl<F> FileSystem for TraceFileSystem<F>
39where
40    F: FileSystem,
41{
42    #[tracing::instrument(level = "trace", skip(self), err)]
43    fn readlink(&self, path: &std::path::Path) -> crate::Result<PathBuf> {
44        self.0.readlink(path)
45    }
46
47    #[tracing::instrument(level = "trace", skip(self), err)]
48    fn read_dir(&self, path: &std::path::Path) -> crate::Result<crate::ReadDir> {
49        self.0.read_dir(path)
50    }
51
52    #[tracing::instrument(level = "trace", skip(self), err)]
53    fn create_dir(&self, path: &std::path::Path) -> crate::Result<()> {
54        self.0.create_dir(path)
55    }
56
57    #[tracing::instrument(level = "trace", skip(self), err)]
58    fn remove_dir(&self, path: &std::path::Path) -> crate::Result<()> {
59        self.0.remove_dir(path)
60    }
61
62    #[tracing::instrument(level = "trace", skip(self), err)]
63    fn rename<'a>(
64        &'a self,
65        from: &'a std::path::Path,
66        to: &'a std::path::Path,
67    ) -> BoxFuture<'a, crate::Result<()>> {
68        Box::pin(async { self.0.rename(from, to).await })
69    }
70
71    #[tracing::instrument(level = "trace", skip(self), err)]
72    fn metadata(&self, path: &std::path::Path) -> crate::Result<crate::Metadata> {
73        self.0.metadata(path)
74    }
75
76    #[tracing::instrument(level = "trace", skip(self), err)]
77    fn symlink_metadata(&self, path: &std::path::Path) -> crate::Result<crate::Metadata> {
78        self.0.symlink_metadata(path)
79    }
80
81    #[tracing::instrument(level = "trace", skip(self), err)]
82    fn remove_file(&self, path: &std::path::Path) -> crate::Result<()> {
83        self.0.remove_file(path)
84    }
85
86    #[tracing::instrument(level = "trace", skip(self))]
87    fn new_open_options(&self) -> crate::OpenOptions<'_> {
88        crate::OpenOptions::new(self)
89    }
90
91    #[tracing::instrument(level = "trace", skip(self))]
92    fn mount(
93        &self,
94        name: String,
95        path: &Path,
96        fs: Box<dyn FileSystem + Send + Sync>,
97    ) -> crate::Result<()> {
98        self.0.mount(name, path, fs)
99    }
100}
101
102impl<F> FileOpener for TraceFileSystem<F>
103where
104    F: FileSystem,
105{
106    #[tracing::instrument(level = "trace", skip(self))]
107    fn open(
108        &self,
109        path: &std::path::Path,
110        conf: &OpenOptionsConfig,
111    ) -> crate::Result<Box<dyn crate::VirtualFile + Send + Sync + 'static>> {
112        let file = self.0.new_open_options().options(conf.clone()).open(path)?;
113        Ok(Box::new(TraceFile {
114            file,
115            path: path.to_owned(),
116        }))
117    }
118}
119
120#[derive(Debug)]
121struct TraceFile {
122    path: PathBuf,
123    file: Box<dyn crate::VirtualFile + Send + Sync + 'static>,
124}
125
126impl VirtualFile for TraceFile {
127    #[tracing::instrument(level = "trace", skip(self), fields(path=%self.path.display()))]
128    fn last_accessed(&self) -> u64 {
129        self.file.last_accessed()
130    }
131
132    #[tracing::instrument(level = "trace", skip(self), fields(path=%self.path.display()))]
133    fn last_modified(&self) -> u64 {
134        self.file.last_modified()
135    }
136
137    #[tracing::instrument(level = "trace", skip(self), fields(path=%self.path.display()))]
138    fn created_time(&self) -> u64 {
139        self.file.created_time()
140    }
141
142    #[tracing::instrument(level = "trace", skip(self), fields(path=%self.path.display()))]
143    fn set_times(&mut self, atime: Option<u64>, mtime: Option<u64>) -> crate::Result<()> {
144        self.file.set_times(atime, mtime)
145    }
146
147    #[tracing::instrument(level = "trace", skip(self), fields(path=%self.path.display()))]
148    fn size(&self) -> u64 {
149        self.file.size()
150    }
151
152    #[tracing::instrument(level = "trace", skip(self), fields(path=%self.path.display()), err)]
153    fn set_len(&mut self, new_size: u64) -> crate::Result<()> {
154        self.file.set_len(new_size)
155    }
156
157    fn unlink(&mut self) -> crate::Result<()> {
158        self.file.unlink()
159    }
160
161    #[tracing::instrument(level = "trace", skip_all, fields(path=%self.path.display()))]
162    fn poll_read_ready(
163        mut self: Pin<&mut Self>,
164        cx: &mut Context<'_>,
165    ) -> Poll<std::io::Result<usize>> {
166        let result = Pin::new(&mut *self.file).poll_read_ready(cx);
167
168        if let Poll::Ready(Err(e)) = &result {
169            tracing::trace!(error = e as &dyn std::error::Error);
170        }
171
172        result
173    }
174
175    #[tracing::instrument(level = "trace", skip_all, fields(path=%self.path.display()))]
176    fn poll_write_ready(
177        mut self: Pin<&mut Self>,
178        cx: &mut Context<'_>,
179    ) -> Poll<std::io::Result<usize>> {
180        let result = Pin::new(&mut *self.file).poll_write_ready(cx);
181
182        if let Poll::Ready(Err(e)) = &result {
183            tracing::trace!(error = e as &dyn std::error::Error);
184        }
185
186        result
187    }
188}
189
190impl AsyncRead for TraceFile {
191    #[tracing::instrument(level = "trace", skip_all, fields(path=%self.path.display()))]
192    fn poll_read(
193        mut self: Pin<&mut Self>,
194        cx: &mut Context<'_>,
195        buf: &mut ReadBuf<'_>,
196    ) -> Poll<std::io::Result<()>> {
197        let result = Pin::new(&mut *self.file).poll_read(cx, buf);
198
199        if let Poll::Ready(Err(e)) = &result {
200            tracing::trace!(error = e as &dyn std::error::Error);
201        }
202
203        result
204    }
205}
206
207impl AsyncWrite for TraceFile {
208    #[tracing::instrument(level = "trace", skip_all, fields(path=%self.path.display()))]
209    fn poll_write(
210        mut self: Pin<&mut Self>,
211        cx: &mut Context<'_>,
212        buf: &[u8],
213    ) -> Poll<Result<usize, std::io::Error>> {
214        let result = Pin::new(&mut *self.file).poll_write(cx, buf);
215
216        if let Poll::Ready(Err(e)) = &result {
217            tracing::trace!(error = e as &dyn std::error::Error);
218        }
219
220        result
221    }
222
223    #[tracing::instrument(level = "trace", skip_all, fields(path=%self.path.display()))]
224    fn poll_flush(
225        mut self: Pin<&mut Self>,
226        cx: &mut Context<'_>,
227    ) -> Poll<Result<(), std::io::Error>> {
228        let result = Pin::new(&mut *self.file).poll_flush(cx);
229
230        if let Poll::Ready(Err(e)) = &result {
231            tracing::trace!(error = e as &dyn std::error::Error);
232        }
233
234        result
235    }
236
237    #[tracing::instrument(level = "trace", skip_all, fields(path=%self.path.display()))]
238    fn poll_shutdown(
239        mut self: Pin<&mut Self>,
240        cx: &mut Context<'_>,
241    ) -> Poll<Result<(), std::io::Error>> {
242        let result = Pin::new(&mut *self.file).poll_shutdown(cx);
243
244        if let Poll::Ready(Err(e)) = &result {
245            tracing::trace!(error = e as &dyn std::error::Error);
246        }
247
248        result
249    }
250}
251
252impl AsyncSeek for TraceFile {
253    #[tracing::instrument(level = "trace", skip_all, fields(path=%self.path.display()), err)]
254    fn start_seek(mut self: Pin<&mut Self>, position: std::io::SeekFrom) -> std::io::Result<()> {
255        Pin::new(&mut *self.file).start_seek(position)
256    }
257
258    #[tracing::instrument(level = "trace", skip_all, fields(path=%self.path.display()))]
259    fn poll_complete(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<u64>> {
260        let result = Pin::new(&mut *self.file).poll_complete(cx);
261
262        if let Poll::Ready(Err(e)) = &result {
263            tracing::trace!(error = e as &dyn std::error::Error);
264        }
265
266        result
267    }
268}