Skip to main content

borgbackup/output/
logging.rs

1//! The logging definitions
2
3use std::fmt::{Display, Formatter};
4
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7
8/// All valid logging messages are defined here
9#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
10#[serde(tag = "type")]
11pub enum LoggingMessage {
12    /// Any regular log output invokes this type.
13    /// Regular log options and filtering applies to these as well.
14    #[serde(rename = "log_message")]
15    LogMessage {
16        /// Unix timestamp
17        time: f64,
18        /// The loglevel of borg
19        #[serde(rename = "levelname")]
20        level_name: LevelName,
21        /// Name of the emitting entity
22        name: String,
23        /// Formatted log message
24        message: String,
25        /// Message ID.
26        ///
27        /// Specifies more information or error information
28        #[serde(rename = "msgid")]
29        msg_id: Option<MessageId>,
30    },
31    /// This is only output by borg create and borg recreate if --list is specified.
32    /// The usual rules for the file listing applies, including the --filter option.
33    #[serde(rename = "file_status")]
34    FileStatus {
35        /// Single-character status as for regular list output
36        status: String,
37        /// Path of the file system object
38        path: String,
39    },
40    /// Absolute progress information with defined end/total and current value.
41    #[serde(rename = "progress_percent")]
42    ProgressPercent {
43        /// unique, opaque integer ID of the operation
44        operation: u64,
45        /// Message ID of the operation
46        #[serde(rename = "msgid")]
47        msg_id: Option<MessageId>,
48        /// Unix timestamp
49        time: f64,
50        /// Indicating whether the operation has finished,
51        /// only the last object for an operation can have this property set to true.
52        finished: bool,
53        /// Current value
54        ///
55        /// absent for finished == true
56        current: Option<u64>,
57        /// Total value
58        ///
59        /// absent for finished == true
60        total: Option<u64>,
61        /// Array that describes the current item, may be null, contents depend on msg_id
62        ///
63        /// absent for finished == true
64        info: Option<Vec<Value>>,
65    },
66    /// A message-based progress information with no concrete progress information,
67    /// just a message saying what is currently being worked on.
68    #[serde(rename = "progress_message")]
69    ProgressMessage {
70        /// unique, opaque integer ID of the operation
71        operation: u64,
72        /// Message ID of the operation
73        #[serde(rename = "msgid")]
74        msg_id: Option<MessageId>,
75        /// Indicating whether the operation has finished,
76        /// only the last object for an operation can have this property set to true.
77        finished: bool,
78        /// current progress message (may be empty/absent)
79        message: Option<String>,
80        /// Unix timestamp
81        time: f64,
82    },
83    /// Output during operations creating archives (borg create and borg recreate)
84    #[serde(rename = "archive_progress")]
85    ArchiveProgress {
86        /// Original size of data processed so far
87        /// before compression and deduplication, may be empty/absent
88        original_size: Option<u64>,
89        /// Compressed size (may be empty/absent)
90        compressed_size: Option<u64>,
91        /// Deduplicated size (may be empty/absent)
92        deduplicated_size: Option<u64>,
93        /// Number of (regular) files processed so far (may be empty/absent)
94        nfiles: Option<u64>,
95        /// Current path (may be empty/absent)
96        path: Option<String>,
97        /// Unix timestamp
98        time: f64,
99        /// Indicating whether the operation has finished,
100        /// only the last object for an operation can have this property set to true.
101        finished: bool,
102    },
103    /// Due to a bug in borg, umount failures are directly reported from
104    /// fusermount instead of being logged as json.
105    UMountError(String),
106}
107
108impl LoggingMessage {
109    /// Given a borg json log, attempt to parse it into a LogMessage
110    pub(crate) fn from_str(log_message: &str) -> Result<Self, serde_json::Error> {
111        // XXX: There's a bug in borg umount where fusermount errors are not
112        //      logged as json so we need to catch them here.
113        if log_message.starts_with("fusermount: entry for") {
114            Ok(LoggingMessage::UMountError(log_message.to_string()))
115        } else {
116            serde_json::from_str(log_message)
117        }
118    }
119}
120/// The valid loglevel of borg
121#[derive(Deserialize, Serialize, Debug, Clone, Copy, PartialEq)]
122#[serde(rename_all = "UPPERCASE")]
123pub enum LevelName {
124    /// Debug level
125    Debug,
126    /// Info level
127    Info,
128    /// Warning level
129    Warning,
130    /// Error level
131    Error,
132    /// Critical level
133    Critical,
134}
135
136/// Message IDs are strings that essentially give a log message or operation a name,
137/// without actually using the full text, since texts change more frequently.
138///
139/// Message IDs are unambiguous and reduce the need to parse log messages.
140#[derive(Deserialize, Serialize, Debug, Clone, Copy, PartialEq)]
141pub enum MessageId {
142    /// Archive {} already exists
143    #[serde(rename = "Archive.AlreadyExists")]
144    ArchiveAlreadyExists,
145    /// Archive {} does not exist
146    #[serde(rename = "Archive.DoesNotExist")]
147    ArchiveDoesNotExist,
148    /// Failed to encode filename “{}” into file system encoding “{}”.
149    /// Consider configuring the LANG environment variable.
150    #[serde(rename = "Archive.IncompatibleFilesystemEncodingError")]
151    ArchiveIncompatibleFilesystemEncodingError,
152    /// Obscure ENOENT error
153    BackupFileNotFoundError,
154    /// Cache initialization aborted
155    #[serde(rename = "Cache.CacheInitAbortedError")]
156    CacheCacheInitAbortedError,
157    /// Repository encryption method changed since last access, refusing to continue
158    #[serde(rename = "Cache.EncryptionMethodMismatch")]
159    CacheEncryptionMethodMismatch,
160    /// Repository access aborted
161    #[serde(rename = "Cache.RepositoryAccessAborted")]
162    CacheRepositoryAccessAborted,
163    /// Cache is newer than repository - do you have multiple,
164    /// independently updated repos with same ID?
165    #[serde(rename = "Cache.RepositoryIDNotUnique")]
166    CacheRepositoryIDNotUnique,
167    /// Cache is newer than repository - this is either an attack
168    /// or unsafe (multiple repos with same ID)
169    #[serde(rename = "Cache.RepositoryReplay")]
170    CacheRepositoryReplay,
171    /// Requested buffer size {} is above the limit of {}.
172    #[serde(rename = "Buffer.MemoryLimitExceeded")]
173    BufferMemoryLimitExceeded,
174    /// The Borg binary extension modules do not seem to be properly installed
175    ExtensionModuleError,
176    /// Data integrity error: {}
177    IntegrityError,
178    /// Repository has no manifest.
179    NoManifestError,
180    /// Formatting Error: “{}”.format({}): {}({})
181    PlaceholderError,
182    /// Invalid key file for repository {} found in {}.
183    KeyfileInvalidError,
184    /// Mismatch between repository {} and key file {}.
185    KeyfileMismatchError,
186    /// No key file for repository {} found in {}.
187    KeyfileNotFoundError,
188    /// passphrase supplied in BORG_PASSPHRASE is incorrect
189    PassphraseWrong,
190    /// exceeded the maximum password retries
191    PasswordRetriesExceeded,
192    /// No key entry found in the config of repository {}.
193    RepoKeyNotFoundError,
194    /// Unsupported manifest envelope. A newer version is required to access this repository.
195    UnsupportedManifestError,
196    /// Unsupported payload type {}. A newer version is required to access this repository.
197    UnsupportedPayloadError,
198    /// This file is not a borg key backup, aborting.
199    NotABorgKeyFile,
200    /// This key backup seems to be for a different backup repository, aborting.
201    RepoIdMismatch,
202    /// Key-management not available for unencrypted repositories.
203    UnencryptedRepo,
204    /// Keytype {0} is unknown.
205    UnknownKeyType,
206    /// Failed to acquire the lock {}.
207    LockError,
208    /// Failed to acquire the lock {}.
209    LockErrorT,
210    /// Connection closed by remote host
211    ConnectionClosed,
212    /// RPC method {} is not valid
213    InvalidRPCMethod,
214    /// Repository path not allowed
215    PathNotAllowed,
216    /// No passphrase was provided for an encrypted repository
217    NoPassphraseFailure,
218    /// Borg server is too old for {}. Required version {}
219    #[serde(rename = "RemoteRepository.RPCServerOutdated")]
220    RemoteRepositoryRPCServerOutdated,
221    /// Borg {}: Got unexpected RPC data format from client.
222    UnexpectedRPCDataFormatFromClient,
223    /// Got unexpected RPC data format from server: {}
224    UnexpectedRPCDataFormatFromServer,
225    /// Repository {} already exists.
226    #[serde(rename = "Repository.AlreadyExists")]
227    RepositoryAlreadyExists,
228    /// Inconsistency detected. Please run “borg check {}”.
229    #[serde(rename = "Repository.CheckNeeded")]
230    RepositoryCheckNeeded,
231    /// Repository {} does not exist.
232    #[serde(rename = "Repository.DoesNotExist")]
233    RepositoryDoesNotExist,
234    /// Insufficient free space to complete transaction (required: {}, available: {}).
235    #[serde(rename = "Repository.InsufficientFreeSpaceError")]
236    RepositoryInsufficientFreeSpaceError,
237    /// {} is not a valid repository. Check repo config.
238    #[serde(rename = "Repository.InvalidRepository")]
239    RepositoryInvalidRepository,
240    /// Attic repository detected. Please run “borg upgrade {}”.
241    #[serde(rename = "Repository.AtticRepository")]
242    RepositoryAtticRepository,
243    /// Object with key {} not found in repository {}.
244    #[serde(rename = "Repository.ObjectNotFound")]
245    RepositoryObjectNotFound,
246    /// Begin a cache transaction
247    #[serde(rename = "cache.begin_transaction")]
248    CacheBeginTransaction,
249    /// appears with borg create --no-cache-sync
250    #[serde(rename = "cache.download_chunks")]
251    CacheDownloadChunks,
252    /// TODO
253    #[serde(rename = "cache.commit")]
254    CacheCommit,
255    /// info is one string element, the name of the archive currently synced.
256    #[serde(rename = "cache.sync")]
257    CacheSync,
258    /// TODO
259    #[serde(rename = "repository.compact_segments")]
260    RepositoryCompactSegments,
261    /// TODO
262    #[serde(rename = "repository.replay_segments")]
263    RepositoryReplaySegments,
264    /// TODO
265    #[serde(rename = "repository.check")]
266    RepositoryCheck,
267    /// TODO
268    #[serde(rename = "check.verify_data")]
269    CheckVerifyData,
270    /// TODO
271    #[serde(rename = "check.rebuild_manifest")]
272    CheckRebuildManifest,
273    /// info is one string element, the name of the path currently extracted.
274    #[serde(rename = "extract")]
275    Extract,
276    /// TODO
277    #[serde(rename = "extract.permissions")]
278    ExtractPermissions,
279    /// TODO
280    #[serde(rename = "archive.delete")]
281    ArchiveDelete,
282    /// TODO
283    #[serde(rename = "archive.calc_stats")]
284    ArchiveCalcStats,
285    /// TODO
286    #[serde(rename = "prune")]
287    Prune,
288    /// TODO
289    #[serde(rename = "upgrade.convert_segments")]
290    UpgradeConvertSegments,
291}
292
293impl Display for MessageId {
294    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
295        match self {
296            MessageId::ArchiveAlreadyExists => write!(f, "Archive.AlreadyExists"),
297            MessageId::ArchiveDoesNotExist => write!(f, "Archive.DoesNotExist"),
298            MessageId::ArchiveIncompatibleFilesystemEncodingError => {
299                write!(f, "Archive.IncompatibleFilesystemEncodingError")
300            }
301            MessageId::BackupFileNotFoundError => write!(f, "BackupFileNotFoundError"),
302            MessageId::CacheCacheInitAbortedError => write!(f, "Cache.CacheInitAbortedError"),
303            MessageId::CacheEncryptionMethodMismatch => write!(f, "Cache.EncryptionMethodMismatch"),
304            MessageId::CacheRepositoryAccessAborted => write!(f, "Cache.RepositoryAccessAborted"),
305            MessageId::CacheRepositoryIDNotUnique => write!(f, "Cache.RepositoryIDNotUnique"),
306            MessageId::CacheRepositoryReplay => write!(f, "Cache.RepositoryReplay"),
307            MessageId::BufferMemoryLimitExceeded => write!(f, "Buffer.MemoryLimitExceeded"),
308            MessageId::ExtensionModuleError => write!(f, "ExtensionModuleError"),
309            MessageId::IntegrityError => write!(f, "IntegrityError"),
310            MessageId::NoManifestError => write!(f, "NoManifestError"),
311            MessageId::PlaceholderError => write!(f, "PlaceholderError"),
312            MessageId::KeyfileInvalidError => write!(f, "KeyfileInvalidError"),
313            MessageId::KeyfileMismatchError => write!(f, "KeyfileMismatchError"),
314            MessageId::KeyfileNotFoundError => write!(f, "KeyfileNotFoundError"),
315            MessageId::PassphraseWrong => write!(f, "PassphraseWrong"),
316            MessageId::PasswordRetriesExceeded => write!(f, "PasswordRetriesExceeded"),
317            MessageId::RepoKeyNotFoundError => write!(f, "RepoKeyNotFoundError"),
318            MessageId::UnsupportedManifestError => write!(f, "UnsupportedManifestError"),
319            MessageId::UnsupportedPayloadError => write!(f, "UnsupportedPayloadError"),
320            MessageId::NotABorgKeyFile => write!(f, "NotABorgKeyFile"),
321            MessageId::RepoIdMismatch => write!(f, "RepoIdMismatch"),
322            MessageId::UnencryptedRepo => write!(f, "UnencryptedRepo"),
323            MessageId::UnknownKeyType => write!(f, "UnknownKeyType"),
324            MessageId::LockError => write!(f, "LockError"),
325            MessageId::LockErrorT => write!(f, "LockErrorT"),
326            MessageId::ConnectionClosed => write!(f, "ConnectionClosed"),
327            MessageId::InvalidRPCMethod => write!(f, "InvalidRPCMethod"),
328            MessageId::PathNotAllowed => write!(f, "PathNotAllowed"),
329            MessageId::NoPassphraseFailure => write!(f, "NoPassphraseFailure"),
330            MessageId::RemoteRepositoryRPCServerOutdated => {
331                write!(f, "RemoteRepository.RPCServerOutdated")
332            }
333            MessageId::UnexpectedRPCDataFormatFromClient => {
334                write!(f, "UnexpectedRPCDataFormatFromClient")
335            }
336            MessageId::UnexpectedRPCDataFormatFromServer => {
337                write!(f, "UnexpectedRPCDataFormatFromServer")
338            }
339            MessageId::RepositoryAlreadyExists => write!(f, "Repository.AlreadyExists"),
340            MessageId::RepositoryCheckNeeded => write!(f, "Repository.CheckNeeded"),
341            MessageId::RepositoryDoesNotExist => write!(f, "Repository.DoesNotExist"),
342            MessageId::RepositoryInsufficientFreeSpaceError => {
343                write!(f, "Repository.InsufficientFreeSpaceError")
344            }
345            MessageId::RepositoryInvalidRepository => write!(f, "Repository.InvalidRepository"),
346            MessageId::RepositoryAtticRepository => write!(f, "Repository.AtticRepository"),
347            MessageId::RepositoryObjectNotFound => write!(f, "Repository.ObjectNotFound"),
348            MessageId::CacheBeginTransaction => write!(f, "cache.begin_transaction"),
349            MessageId::CacheDownloadChunks => write!(f, "cache.download_chunks"),
350            MessageId::CacheCommit => write!(f, "cache.commit"),
351            MessageId::CacheSync => write!(f, "cache.sync"),
352            MessageId::RepositoryCompactSegments => write!(f, "repository.compact_segments"),
353            MessageId::RepositoryReplaySegments => write!(f, "repository.replay_segments"),
354            MessageId::RepositoryCheck => write!(f, "repository.check"),
355            MessageId::CheckVerifyData => write!(f, "check.verify_data"),
356            MessageId::CheckRebuildManifest => write!(f, "check.rebuild_manifest"),
357            MessageId::Extract => write!(f, "extract"),
358            MessageId::ExtractPermissions => write!(f, "extract.permissions"),
359            MessageId::ArchiveDelete => write!(f, "archive.delete"),
360            MessageId::ArchiveCalcStats => write!(f, "archive.calc_stats"),
361            MessageId::Prune => write!(f, "prune"),
362            MessageId::UpgradeConvertSegments => write!(f, "upgrade.convert_segments"),
363        }
364    }
365}
366
367#[cfg(test)]
368mod tests {
369    use super::LoggingMessage;
370
371    #[test]
372    fn test_log_message_parse_mount() {
373        let message = "fusermount: entry for /home/david/fake-directory not found in /etc/mtab";
374        let log = LoggingMessage::from_str(message);
375        assert_eq!(
376            LoggingMessage::UMountError(message.to_string()),
377            log.expect("Expected LoggingMessage::UMountError to be parsed correctly")
378        )
379    }
380}