pEpUtilities/pEpUtilities/pEpUtilities/Log/Logger.swift
author Xavier Algarra <xavier@pep-project.org>
Tue, 29 Jan 2019 15:54:56 +0100
branchIOS-1455
changeset 7578 d63b6c9b5735
child 7579 2cace0d2845d
permissions -rw-r--r--
IOS-1455 temp crashing commit
xavier@7578
     1
//
xavier@7578
     2
//  Logger.swift
xavier@7578
     3
//  pEp
xavier@7578
     4
//
xavier@7578
     5
//  Created by Dirk Zimmermann on 18.12.18.
xavier@7578
     6
//  Copyright © 2018 p≡p Security S.A. All rights reserved.
xavier@7578
     7
//
xavier@7578
     8
xavier@7578
     9
import Foundation
xavier@7578
    10
import os.log
xavier@7578
    11
import asl
xavier@7578
    12
//import pEpUtilities
xavier@7578
    13
xavier@7578
    14
//import MessageModel // For SystemUtils.crash only
xavier@7578
    15
xavier@7578
    16
/**
xavier@7578
    17
 Thin layer over `os_log` or `asl_logger` where not available.
xavier@7578
    18
 The fallback to asl is only in effect for iOS 9, and currently
xavier@7578
    19
 doesn't appear anywhere visible on that platform.
xavier@7578
    20
 */
xavier@7578
    21
public class Logger {
xavier@7578
    22
    /**
xavier@7578
    23
     Map `os_log` levels.
xavier@7578
    24
     */
xavier@7578
    25
    public enum Severity {
xavier@7578
    26
        /**
xavier@7578
    27
         - Note: Not persisted by default, but will be written in case of errors.
xavier@7578
    28
         */
xavier@7578
    29
        case info
xavier@7578
    30
xavier@7578
    31
        /**
xavier@7578
    32
         - Note: Not persisted by default, but will be written in case of errors.
xavier@7578
    33
         */
xavier@7578
    34
        case debug
xavier@7578
    35
xavier@7578
    36
        /**
xavier@7578
    37
         This is the lowest priority that gets written to disk by default.
xavier@7578
    38
         Used like WARN in this logger.
xavier@7578
    39
         */
xavier@7578
    40
        case `default`
xavier@7578
    41
xavier@7578
    42
        case error
xavier@7578
    43
xavier@7578
    44
        /**
xavier@7578
    45
         - Note: As this is referring to inter-process problems, I don't see a use-case
xavier@7578
    46
         for iOS.
xavier@7578
    47
         */
xavier@7578
    48
        case fault
xavier@7578
    49
xavier@7578
    50
        @available(iOS 10.0, macOS 10.12, tvOS 10.0, watchOS 3.0, *)
xavier@7578
    51
        public func osLogType() -> OSLogType {
xavier@7578
    52
            switch self {
xavier@7578
    53
            case .info:
xavier@7578
    54
                return .info
xavier@7578
    55
            case .debug:
xavier@7578
    56
                return .debug
xavier@7578
    57
            case .default:
xavier@7578
    58
                return .default
xavier@7578
    59
            case .error:
xavier@7578
    60
                return .error
xavier@7578
    61
            case .fault:
xavier@7578
    62
                return .fault
xavier@7578
    63
            }
xavier@7578
    64
        }
xavier@7578
    65
xavier@7578
    66
        /**
xavier@7578
    67
         Maps the internal criticality of a log  message into a subsystem of ASL levels.
xavier@7578
    68
xavier@7578
    69
         ASL has the following:
xavier@7578
    70
         * ASL_LEVEL_EMERG
xavier@7578
    71
         * ASL_LEVEL_ALERT
xavier@7578
    72
         * ASL_LEVEL_CRIT
xavier@7578
    73
         * ASL_LEVEL_ERR
xavier@7578
    74
         * ASL_LEVEL_WARNING
xavier@7578
    75
         * ASL_LEVEL_NOTICE
xavier@7578
    76
         * ASL_LEVEL_INFO
xavier@7578
    77
         * ASL_LEVEL_DEBUG
xavier@7578
    78
         */
xavier@7578
    79
        public func aslLevelString() -> String {
xavier@7578
    80
            switch self {
xavier@7578
    81
            case .default:
xavier@7578
    82
                return "ASL_LEVEL_NOTICE"
xavier@7578
    83
            case .info:
xavier@7578
    84
                return "ASL_LEVEL_INFO"
xavier@7578
    85
            case .debug:
xavier@7578
    86
                return "ASL_LEVEL_DEBUG"
xavier@7578
    87
            case .error:
xavier@7578
    88
                return "ASL_LEVEL_ERR"
xavier@7578
    89
            case .fault:
xavier@7578
    90
                return "ASL_LEVEL_CRIT"
xavier@7578
    91
            }
xavier@7578
    92
        }
xavier@7578
    93
    }
xavier@7578
    94
xavier@7578
    95
    // move this loggers to the app
xavier@7578
    96
xavier@7578
    97
    public static let frontendLogger = Logger(category: "frontend")
xavier@7578
    98
    public static let backendLogger = Logger(category: "backend")
xavier@7578
    99
    public static let utilLogger = Logger(category: "util")
xavier@7578
   100
    public static let htmlParsingLogger = Logger(category: "htmlParsing")
xavier@7578
   101
    public static let modelLogger = Logger(category: "model")
xavier@7578
   102
    public static let appDelegateLogger = Logger(category: "appDelegate")
xavier@7578
   103
xavier@7578
   104
    public init(subsystem: String = "security.pEp.app.iOS", category: String) {
xavier@7578
   105
        self.subsystem = subsystem
xavier@7578
   106
        self.category = category
xavier@7578
   107
        if #available(iOS 10.0, macOS 10.12, tvOS 10.0, watchOS 3.0, *) {
xavier@7578
   108
            osLogger = OSLog(subsystem: subsystem, category: category)
xavier@7578
   109
        } else {
xavier@7578
   110
            osLogger = nil
xavier@7578
   111
        }
xavier@7578
   112
    }
xavier@7578
   113
xavier@7578
   114
    /**
xavier@7578
   115
     Logs to default.
xavier@7578
   116
     */
xavier@7578
   117
    public func log(function: String = #function,
xavier@7578
   118
                    filePath: String = #file,
xavier@7578
   119
                    fileLine: Int = #line,
xavier@7578
   120
                    _ message: StaticString,
xavier@7578
   121
                    _ args: CVarArg...) {
xavier@7578
   122
        saveLog(message: message,
xavier@7578
   123
                severity: .default,
xavier@7578
   124
                function: function,
xavier@7578
   125
                filePath: filePath,
xavier@7578
   126
                fileLine: fileLine,
xavier@7578
   127
                args: args)
xavier@7578
   128
    }
xavier@7578
   129
xavier@7578
   130
    /**
xavier@7578
   131
     os_log doesn't have a warn per se, but default is coming close.
xavier@7578
   132
     This is the same as log.
xavier@7578
   133
     */
xavier@7578
   134
    public func warn(function: String = #function,
xavier@7578
   135
                     filePath: String = #file,
xavier@7578
   136
                     fileLine: Int = #line,
xavier@7578
   137
                     _ message: StaticString,
xavier@7578
   138
                     _ args: CVarArg...) {
xavier@7578
   139
        saveLog(message: message,
xavier@7578
   140
                severity: .default,
xavier@7578
   141
                function: function,
xavier@7578
   142
                filePath: filePath,
xavier@7578
   143
                fileLine: fileLine,
xavier@7578
   144
                args: args)
xavier@7578
   145
    }
xavier@7578
   146
xavier@7578
   147
    /**
xavier@7578
   148
     Logs to info.
xavier@7578
   149
     */
xavier@7578
   150
    public func info(function: String = #function,
xavier@7578
   151
                     filePath: String = #file,
xavier@7578
   152
                     fileLine: Int = #line,
xavier@7578
   153
                     _ message: StaticString,
xavier@7578
   154
                     _ args: CVarArg...) {
xavier@7578
   155
        saveLog(message: message,
xavier@7578
   156
                severity: .info,
xavier@7578
   157
                function: function,
xavier@7578
   158
                filePath: filePath,
xavier@7578
   159
                fileLine: fileLine,
xavier@7578
   160
                args: args)
xavier@7578
   161
    }
xavier@7578
   162
xavier@7578
   163
    /**
xavier@7578
   164
     Logs to debug.
xavier@7578
   165
     */
xavier@7578
   166
    public func debug(function: String = #function,
xavier@7578
   167
                      filePath: String = #file,
xavier@7578
   168
                      fileLine: Int = #line,
xavier@7578
   169
                      _ message: StaticString,
xavier@7578
   170
                      _ args: CVarArg...) {
xavier@7578
   171
        saveLog(message: message,
xavier@7578
   172
                severity: .debug,
xavier@7578
   173
                function: function,
xavier@7578
   174
                filePath: filePath,
xavier@7578
   175
                fileLine: fileLine,
xavier@7578
   176
                args: args)
xavier@7578
   177
    }
xavier@7578
   178
xavier@7578
   179
    /**
xavier@7578
   180
     Logs to error.
xavier@7578
   181
     */
xavier@7578
   182
    public func error(function: String = #function,
xavier@7578
   183
                      filePath: String = #file,
xavier@7578
   184
                      fileLine: Int = #line,
xavier@7578
   185
                      _ message: StaticString,
xavier@7578
   186
                      _ args: CVarArg...) {
xavier@7578
   187
        saveLog(message: message,
xavier@7578
   188
                severity: .error,
xavier@7578
   189
                function: function,
xavier@7578
   190
                filePath: filePath,
xavier@7578
   191
                fileLine: fileLine,
xavier@7578
   192
                args: args)
xavier@7578
   193
    }
xavier@7578
   194
xavier@7578
   195
    /**
xavier@7578
   196
     Logs to fault.
xavier@7578
   197
     */
xavier@7578
   198
    public func fault(function: String = #function,
xavier@7578
   199
                      filePath: String = #file,
xavier@7578
   200
                      fileLine: Int = #line,
xavier@7578
   201
                      _ message: StaticString,
xavier@7578
   202
                      _ args: CVarArg...) {
xavier@7578
   203
        saveLog(message: message,
xavier@7578
   204
                severity: .fault,
xavier@7578
   205
                function: function,
xavier@7578
   206
                filePath: filePath,
xavier@7578
   207
                fileLine: fileLine,
xavier@7578
   208
                args: args)
xavier@7578
   209
    }
xavier@7578
   210
xavier@7578
   211
    public func errorAndCrash(function: String = #function,
xavier@7578
   212
                              filePath: String = #file,
xavier@7578
   213
                              fileLine: Int = #line,
xavier@7578
   214
                              _ message: StaticString,
xavier@7578
   215
                              _ args: CVarArg...) {
xavier@7578
   216
        saveLog(message: message,
xavier@7578
   217
                severity: .fault,
xavier@7578
   218
                function: function,
xavier@7578
   219
                filePath: filePath,
xavier@7578
   220
                fileLine: fileLine,
xavier@7578
   221
                args: args)
xavier@7578
   222
xavier@7578
   223
        // This will omit the arguments, but it's still matchable
xavier@7578
   224
        SystemUtils.crash("\(message)")
xavier@7578
   225
    }
xavier@7578
   226
xavier@7578
   227
    /**
xavier@7578
   228
     Logs an error.
xavier@7578
   229
     */
xavier@7578
   230
    public func log(function: String = #function,
xavier@7578
   231
                    filePath: String = #file,
xavier@7578
   232
                    fileLine: Int = #line,
xavier@7578
   233
                    error: Error) {
xavier@7578
   234
        // Error is not supported by "%@", because it doesn't conform to CVArg
xavier@7578
   235
        // and CVArg is only meant for internal types.
xavier@7578
   236
        // An alternative would be to use localizedDescription(),
xavier@7578
   237
        // but if they are indeed localized you end up with international
xavier@7578
   238
        // log messages.
xavier@7578
   239
        // So we wrap it into an NSError which does suppord CVArg.
xavier@7578
   240
        let nsErr = NSError(domain: subsystem, code: 0, userInfo: [NSUnderlyingErrorKey: error])
xavier@7578
   241
xavier@7578
   242
        saveLog(message: "%{public}@",
xavier@7578
   243
                severity: .default,
xavier@7578
   244
                function: function,
xavier@7578
   245
                filePath: filePath,
xavier@7578
   246
                fileLine: fileLine,
xavier@7578
   247
                args: [nsErr])
xavier@7578
   248
    }
xavier@7578
   249
xavier@7578
   250
    /**
xavier@7578
   251
     Testing only. If you want to test the fallback to ASL logging you may have to call
xavier@7578
   252
     this, as all the logging is deferred to a serial queue.
xavier@7578
   253
     */
xavier@7578
   254
    public func testFlush() {
xavier@7578
   255
        if #available(iOS 10.0, macOS 10.12, tvOS 10.0, watchOS 3.0, *) {
xavier@7578
   256
            // no sense on these versions
xavier@7578
   257
        } else {
xavier@7578
   258
            aslLogQueue.sync {
xavier@7578
   259
                // nothing
xavier@7578
   260
            }
xavier@7578
   261
        }
xavier@7578
   262
    }
xavier@7578
   263
xavier@7578
   264
    /**
xavier@7578
   265
     Since this kind of logging is used so often in the codebase, it has its
xavier@7578
   266
     own method.
xavier@7578
   267
     */
xavier@7578
   268
    public func lostMySelf() {
xavier@7578
   269
        errorAndCrash("Lost MySelf")
xavier@7578
   270
    }
xavier@7578
   271
xavier@7578
   272
    private let subsystem: String
xavier@7578
   273
    private let category: String
xavier@7578
   274
xavier@7578
   275
    private let osLogger: Any?
xavier@7578
   276
xavier@7578
   277
    private func saveLog(message: StaticString,
xavier@7578
   278
                         severity: Severity,
xavier@7578
   279
                         function: String = #function,
xavier@7578
   280
                         filePath: String = #file,
xavier@7578
   281
                         fileLine: Int = #line,
xavier@7578
   282
                         args: [CVarArg]) {
xavier@7578
   283
        if #available(iOS 10.0, macOS 10.12, tvOS 10.0, watchOS 3.0, *) {
xavier@7578
   284
            osLog(message: message,
xavier@7578
   285
                  severity: severity,
xavier@7578
   286
                  function: function,
xavier@7578
   287
                  filePath: filePath,
xavier@7578
   288
                  fileLine: fileLine,
xavier@7578
   289
                  args: args)
xavier@7578
   290
        } else {
xavier@7578
   291
            aslLog(message: message,
xavier@7578
   292
                   severity: severity,
xavier@7578
   293
                   function: function,
xavier@7578
   294
                   filePath: filePath,
xavier@7578
   295
                   fileLine: fileLine,
xavier@7578
   296
                   args: args)
xavier@7578
   297
        }
xavier@7578
   298
    }
xavier@7578
   299
xavier@7578
   300
    /**
xavier@7578
   301
     - Note: If the number of arguments to the format string exceeds 10,
xavier@7578
   302
     the logging doesn't work correctly. Can be easily fixed though, if really needed.
xavier@7578
   303
     */
xavier@7578
   304
    @available(iOS 10.0, macOS 10.12, tvOS 10.0, watchOS 3.0, *)
xavier@7578
   305
    private func osLog(message: StaticString,
xavier@7578
   306
                       severity: Severity,
xavier@7578
   307
                       function: String = #function,
xavier@7578
   308
                       filePath: String = #file,
xavier@7578
   309
                       fileLine: Int = #line,
xavier@7578
   310
                       args: [CVarArg]) {
xavier@7578
   311
        let theLog = osLogger as! OSLog
xavier@7578
   312
        let theType = severity.osLogType()
xavier@7578
   313
xavier@7578
   314
        // I haven't found a way of injecting `function` etc. into the original message for
xavier@7578
   315
        // just one call to `os_log`, so the 'position' is logged on a separate line.
xavier@7578
   316
        os_log("%@:%d %@:",
xavier@7578
   317
               log: theLog,
xavier@7578
   318
               type: theType,
xavier@7578
   319
               filePath,
xavier@7578
   320
               fileLine,
xavier@7578
   321
               function)
xavier@7578
   322
xavier@7578
   323
        // We have to expand the array of arguments into positional ones.
xavier@7578
   324
        // There is no attempt of trying to format the string on our side
xavier@7578
   325
        // in order to make use of `os_log`'s fast 'offline' formatting
xavier@7578
   326
        // (that is, the work is delayed until actual log display).
xavier@7578
   327
        switch args.count {
xavier@7578
   328
        case 0:
xavier@7578
   329
            os_log(message,
xavier@7578
   330
                   log: theLog,
xavier@7578
   331
                   type: theType)
xavier@7578
   332
        case 1:
xavier@7578
   333
            os_log(message,
xavier@7578
   334
                   log: theLog,
xavier@7578
   335
                   type: theType,
xavier@7578
   336
                   args[0])
xavier@7578
   337
        case 2:
xavier@7578
   338
            os_log(message,
xavier@7578
   339
                   log: theLog,
xavier@7578
   340
                   type: theType,
xavier@7578
   341
                   args[0], args[1])
xavier@7578
   342
        case 3:
xavier@7578
   343
            os_log(message,
xavier@7578
   344
                   log: theLog,
xavier@7578
   345
                   type: theType,
xavier@7578
   346
                   args[0], args[1], args[2])
xavier@7578
   347
        case 4:
xavier@7578
   348
            os_log(message,
xavier@7578
   349
                   log: theLog,
xavier@7578
   350
                   type: theType,
xavier@7578
   351
                   args[0], args[1], args[2], args[3])
xavier@7578
   352
        case 5:
xavier@7578
   353
            os_log(message,
xavier@7578
   354
                   log: theLog,
xavier@7578
   355
                   type: theType,
xavier@7578
   356
                   args[0], args[1], args[2], args[3], args[4])
xavier@7578
   357
        case 6:
xavier@7578
   358
            os_log(message,
xavier@7578
   359
                   log: theLog,
xavier@7578
   360
                   type: theType,
xavier@7578
   361
                   args[0], args[1], args[2], args[3], args[4], args[5])
xavier@7578
   362
        case 7:
xavier@7578
   363
            os_log(message,
xavier@7578
   364
                   log: theLog,
xavier@7578
   365
                   type: theType,
xavier@7578
   366
                   args[0], args[1], args[2], args[3], args[4], args[5], args[6])
xavier@7578
   367
        case 8:
xavier@7578
   368
            os_log(message,
xavier@7578
   369
                   log: theLog,
xavier@7578
   370
                   type: theType,
xavier@7578
   371
                   args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7])
xavier@7578
   372
        case 9:
xavier@7578
   373
            os_log(message,
xavier@7578
   374
                   log: theLog,
xavier@7578
   375
                   type: theType,
xavier@7578
   376
                   args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7],
xavier@7578
   377
                   args[8])
xavier@7578
   378
        case 10:
xavier@7578
   379
            os_log(message,
xavier@7578
   380
                   log: theLog,
xavier@7578
   381
                   type: theType,
xavier@7578
   382
                   args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7],
xavier@7578
   383
                   args[8], args[9])
xavier@7578
   384
        default:
xavier@7578
   385
            os_log("Using more than 10 parameters",
xavier@7578
   386
                   log: theLog,
xavier@7578
   387
                   type: .error)
xavier@7578
   388
            os_log(message,
xavier@7578
   389
                   log: theLog,
xavier@7578
   390
                   type: theType,
xavier@7578
   391
                   args)
xavier@7578
   392
        }
xavier@7578
   393
    }
xavier@7578
   394
xavier@7578
   395
    private func aslLog(message: StaticString,
xavier@7578
   396
                        severity: Severity,
xavier@7578
   397
                        function: String = #function,
xavier@7578
   398
                        filePath: String = #file,
xavier@7578
   399
                        fileLine: Int = #line,
xavier@7578
   400
                        args: [CVarArg]) {
xavier@7578
   401
        aslLogQueue.async { [weak self] in
xavier@7578
   402
            if let theSelf = self {
xavier@7578
   403
                let logMessage = asl_new(UInt32(ASL_TYPE_MSG))
xavier@7578
   404
xavier@7578
   405
                theSelf.checkASLSuccess(asl_set(logMessage, ASL_KEY_SENDER, theSelf.subsystem))
xavier@7578
   406
xavier@7578
   407
                theSelf.checkASLSuccess(asl_set(logMessage, ASL_KEY_FACILITY, theSelf.category))
xavier@7578
   408
xavier@7578
   409
                theSelf.checkASLSuccess(asl_set(
xavier@7578
   410
                    logMessage,
xavier@7578
   411
                    ASL_KEY_MSG,
xavier@7578
   412
                    "\(filePath):\(fileLine) \(function): \(message) \(args)"))
xavier@7578
   413
xavier@7578
   414
                theSelf.checkASLSuccess(asl_set(logMessage, ASL_KEY_LEVEL, "ASL_LEVEL_ERROR"))
xavier@7578
   415
xavier@7578
   416
                let nowDate = Date()
xavier@7578
   417
                let dateString = "\(Int(nowDate.timeIntervalSince1970))"
xavier@7578
   418
                theSelf.checkASLSuccess(asl_set(logMessage, ASL_KEY_TIME, dateString))
xavier@7578
   419
xavier@7578
   420
                theSelf.checkASLSuccess(asl_set(logMessage, ASL_KEY_READ_UID, "-1"))
xavier@7578
   421
xavier@7578
   422
                theSelf.checkASLSuccess(asl_send(theSelf.consoleLogger(), logMessage))
xavier@7578
   423
xavier@7578
   424
                asl_free(logMessage)
xavier@7578
   425
            }
xavier@7578
   426
        }
xavier@7578
   427
    }
xavier@7578
   428
xavier@7578
   429
    private var consoleClient: aslclient?
xavier@7578
   430
xavier@7578
   431
    private lazy var aslLogQueue = DispatchQueue(label: "security.pEp.asl.log")
xavier@7578
   432
xavier@7578
   433
    private let sender = "security.pEp.app.iOS"
xavier@7578
   434
xavier@7578
   435
    private func createConsoleLogger() -> asl_object_t {
xavier@7578
   436
        return asl_open(self.sender, subsystem, 0)
xavier@7578
   437
    }
xavier@7578
   438
xavier@7578
   439
    private func consoleLogger() -> aslclient? {
xavier@7578
   440
        if consoleClient == nil {
xavier@7578
   441
            consoleClient = createConsoleLogger()
xavier@7578
   442
        }
xavier@7578
   443
        return consoleClient
xavier@7578
   444
    }
xavier@7578
   445
xavier@7578
   446
    deinit {
xavier@7578
   447
        if consoleClient != nil {
xavier@7578
   448
            asl_free(consoleClient)
xavier@7578
   449
        }
xavier@7578
   450
    }
xavier@7578
   451
xavier@7578
   452
    private func checkASLSuccess(_ result: Int32, comment: String = "no comment") {
xavier@7578
   453
        if result != 0 {
xavier@7578
   454
            print("*** error: \(comment)")
xavier@7578
   455
        }
xavier@7578
   456
    }
xavier@7578
   457
}