summaryrefslogtreecommitdiffhomepage
path: root/ios/MullvadLogging/Logging.swift
blob: 78b74ca25a39b7fb2a6ade69accae8a9fa00bb96 (plain)
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
//
//  Logging.swift
//  MullvadVPN
//
//  Created by pronebird on 02/08/2020.
//  Copyright © 2025 Mullvad VPN AB. All rights reserved.
//

import Foundation
@_exported import Logging
import MullvadTypes

private enum LoggerOutput {
    case fileOutput(_ fileOutput: LogFileOutputStream)
    case osLogOutput(_ subsystem: String)
}

public struct LoggerBuilder {
    private(set) var logRotationErrors: [Error] = []
    private var outputs: [LoggerOutput] = []

    public var metadata: Logger.Metadata = [:]
    public var logLevel: Logger.Level = .debug
    public var header: String

    public init(header: String) {
        self.header = header
    }

    public mutating func addFileOutput(fileURL: URL) {
        let logsDirectoryURL = fileURL.deletingLastPathComponent()

        try? FileManager.default.createDirectory(
            at: logsDirectoryURL,
            withIntermediateDirectories: false,
            attributes: nil
        )

        do {
            try LogRotation.rotateLogs(
                logDirectory: logsDirectoryURL,
                options: LogRotation.Options(
                    storageSizeLimit: 2_000_000,  // 2 MB
                    oldestAllowedDate: Date(timeIntervalSinceNow: -Duration.days(7).timeInterval)
                ))
        } catch {
            logRotationErrors.append(error)
        }

        outputs.append(.fileOutput(LogFileOutputStream(fileURL: fileURL, header: header)))
    }

    public mutating func addOSLogOutput(subsystem: String) {
        outputs.append(.osLogOutput(subsystem))
    }

    public func install() {
        LoggingSystem.bootstrap { label -> LogHandler in
            let logHandlers: [LogHandler] = outputs.map { output in
                switch output {
                case let .fileOutput(stream):
                    return CustomFormatLogHandler(label: label, streams: [stream])

                case let .osLogOutput(subsystem):
                    return OSLogHandler(subsystem: subsystem, category: label)
                }
            }

            if logHandlers.isEmpty {
                return SwiftLogNoOpLogHandler()
            } else {
                var multiplex = MultiplexLogHandler(logHandlers)
                multiplex.metadata = metadata
                multiplex.logLevel = logLevel
                return multiplex
            }
        }

        if !logRotationErrors.isEmpty {
            let rotationLogger = Logger(label: "LogRotation")

            for error in logRotationErrors {
                rotationLogger.error(error: error, message: error.localizedDescription)
            }
        }
    }
}