DDLogHandlerTests.swift 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. // Software License Agreement (BSD License)
  2. //
  3. // Copyright (c) 2010-2025, Deusty, LLC
  4. // All rights reserved.
  5. //
  6. // Redistribution and use of this software in source and binary forms,
  7. // with or without modification, are permitted provided that the following conditions are met:
  8. //
  9. // * Redistributions of source code must retain the above copyright notice,
  10. // this list of conditions and the following disclaimer.
  11. //
  12. // * Neither the name of Deusty nor the names of its contributors may be used
  13. // to endorse or promote products derived from this software without specific
  14. // prior written permission of Deusty, LLC.
  15. import XCTest
  16. import CocoaLumberjack
  17. @testable import Logging
  18. @testable import CocoaLumberjackSwiftLogBackend
  19. fileprivate final class MockDDLog: DDLog, @unchecked Sendable {
  20. struct LoggedMessage: Sendable, Equatable {
  21. let async: Bool
  22. let message: DDLogMessage
  23. }
  24. private(set) var loggedMessages = Array<LoggedMessage>()
  25. override func log(asynchronous: Bool, message logMessage: DDLogMessage) {
  26. super.log(asynchronous: asynchronous, message: logMessage)
  27. loggedMessages.append(LoggedMessage(async: asynchronous, message: logMessage))
  28. }
  29. }
  30. final class DDLogHandlerTests: XCTestCase {
  31. private var mockDDLog: MockDDLog!
  32. private var logSource: String { "CocoaLumberjackSwiftLogBackendTests" }
  33. override func setUp() {
  34. super.setUp()
  35. mockDDLog = MockDDLog()
  36. }
  37. override func tearDown() {
  38. mockDDLog = nil
  39. super.tearDown()
  40. }
  41. func testBootstrappingWithConvenienceMethod() throws {
  42. // It is important that this is the only test using the convenience method,
  43. // since another use of it will fail the precondition (multiple bootstrap calls)
  44. // All other tests must use `LoggingSystem.bootstrapInternal`.
  45. LoggingSystem.bootstrapWithCocoaLumberjack(for: mockDDLog)
  46. let logger = Logging.Logger(label: "TestLogger")
  47. let msg: Logging.Logger.Message = "test message"
  48. let logLine: UInt = #line + 1
  49. logger.info(msg)
  50. XCTAssertEqual(mockDDLog.loggedMessages.count, 1)
  51. let loggedMsg = try XCTUnwrap(mockDDLog.loggedMessages.first)
  52. XCTAssertTrue(loggedMsg.async)
  53. XCTAssertEqual(loggedMsg.message.message, String(describing: msg))
  54. XCTAssertEqual(loggedMsg.message.level, .info)
  55. XCTAssertEqual(loggedMsg.message.flag, .info)
  56. XCTAssertEqual(loggedMsg.message.file, #fileID)
  57. XCTAssertEqual(loggedMsg.message.function, #function)
  58. XCTAssertEqual(loggedMsg.message.line, logLine)
  59. XCTAssertNotNil(loggedMsg.message.swiftLogInfo)
  60. XCTAssertEqual(loggedMsg.message.swiftLogInfo, .init(logger: .init(label: logger.label,
  61. metadataSources: .init(logger: logger.handler.metadata,
  62. provider: logger.metadataProvider?.get())),
  63. message: .init(message: msg,
  64. level: .info,
  65. metadata: nil,
  66. source: logSource)))
  67. }
  68. func testBootstrappingWithExplicitMethod() throws {
  69. LoggingSystem.bootstrapInternal(DDLogHandler.handlerFactory(for: mockDDLog))
  70. let logger = Logging.Logger(label: "TestLogger")
  71. let msg: Logging.Logger.Message = "test message"
  72. let logLine: UInt = #line + 1
  73. logger.info(msg)
  74. XCTAssertEqual(mockDDLog.loggedMessages.count, 1)
  75. let loggedMsg = try XCTUnwrap(mockDDLog.loggedMessages.first)
  76. XCTAssertTrue(loggedMsg.async)
  77. XCTAssertEqual(loggedMsg.message.message, String(describing: msg))
  78. XCTAssertEqual(loggedMsg.message.level, .info)
  79. XCTAssertEqual(loggedMsg.message.flag, .info)
  80. XCTAssertEqual(loggedMsg.message.file, #fileID)
  81. XCTAssertEqual(loggedMsg.message.function, #function)
  82. XCTAssertEqual(loggedMsg.message.line, logLine)
  83. XCTAssertNotNil(loggedMsg.message.swiftLogInfo)
  84. XCTAssertEqual(loggedMsg.message.swiftLogInfo, .init(logger: .init(label: logger.label,
  85. metadataSources: .init(logger: logger.handler.metadata,
  86. provider: logger.metadataProvider?.get())),
  87. message: .init(message: msg,
  88. level: .info,
  89. metadata: nil,
  90. source: logSource)))
  91. }
  92. func testDefaults() throws {
  93. LoggingSystem.bootstrapInternal(DDLogHandler.handlerFactory())
  94. let logger = Logging.Logger(label: "TestLogger")
  95. XCTAssertEqual(logger.logLevel, .info)
  96. XCTAssertTrue(logger.handler is DDLogHandler)
  97. let ddLogHandler = try XCTUnwrap(logger.handler as? DDLogHandler)
  98. XCTAssertEqual(ddLogHandler.loggerInfo.label, logger.label)
  99. XCTAssertTrue(ddLogHandler.loggerInfo.metadataSources.logger.isEmpty)
  100. XCTAssertTrue(ddLogHandler.config.log === DDLog.sharedInstance)
  101. XCTAssertEqual(ddLogHandler.config.syncLogging.tresholdLevel, .error)
  102. XCTAssertEqual(ddLogHandler.config.syncLogging.metadataKey, DDLogHandler.defaultSynchronousLoggingMetadataKey)
  103. }
  104. func testLoggingAllLevels() throws {
  105. let syncTresholdLevel = Logging.Logger.Level.warning
  106. let syncLoggingMetadataKey: Logging.Logger.Metadata.Key = "test-log-sync"
  107. LoggingSystem.bootstrapInternal(DDLogHandler.handlerFactory(for: mockDDLog,
  108. loggingSynchronousAsOf: syncTresholdLevel,
  109. synchronousLoggingMetadataKey: syncLoggingMetadataKey))
  110. var logger = Logging.Logger(label: "TestLogger")
  111. logger.logLevel = .trace // enable all logs
  112. logger[metadataKey: "test-data"] = "test-value"
  113. XCTAssertEqual(logger.logLevel, .trace)
  114. XCTAssertEqual(logger[metadataKey: "test-data"], "test-value")
  115. let allLevels = Logging.Logger.Level.allCases
  116. let message1Meta: Logging.Logger.Metadata = ["msg-data": "msg-value"]
  117. let message2Meta = message1Meta.merging([syncLoggingMetadataKey: .stringConvertible(true)], uniquingKeysWith: { $1 })
  118. let logLine1: UInt = #line + 3
  119. let logLine2 = logLine1 + 1
  120. for level in allLevels {
  121. logger.log(level: level, "\(level)-msg", metadata: message1Meta)
  122. logger.log(level: level, "\(level)-msg-with-sync", metadata: message2Meta)
  123. }
  124. XCTAssertEqual(mockDDLog.loggedMessages.count, Logging.Logger.Level.allCases.count * 2)
  125. guard mockDDLog.loggedMessages.count >= Logging.Logger.Level.allCases.count * 2 else { return } // prevent test crashes
  126. zip(allLevels, stride(from: mockDDLog.loggedMessages.startIndex, to: mockDDLog.loggedMessages.endIndex, by: 2)).forEach {
  127. let level = $0.0
  128. let loggedMsg1 = mockDDLog.loggedMessages[$0.1]
  129. XCTAssertEqual(loggedMsg1.async, level < syncTresholdLevel)
  130. XCTAssertEqual(loggedMsg1.message.message, "\(level)-msg")
  131. XCTAssertEqual(loggedMsg1.message.level, level.ddLogLevelAndFlag.0)
  132. XCTAssertEqual(loggedMsg1.message.flag, level.ddLogLevelAndFlag.1)
  133. XCTAssertEqual(loggedMsg1.message.file, #fileID)
  134. XCTAssertEqual(loggedMsg1.message.function, #function)
  135. XCTAssertEqual(loggedMsg1.message.line, logLine1)
  136. XCTAssertNotNil(loggedMsg1.message.swiftLogInfo)
  137. XCTAssertEqual(loggedMsg1.message.swiftLogInfo, .init(logger: .init(label: logger.label,
  138. metadataSources: .init(logger: logger.handler.metadata,
  139. provider: logger.metadataProvider?.get())),
  140. message: .init(message: "\(level)-msg",
  141. level: level,
  142. metadata: message1Meta,
  143. source: logSource)))
  144. let loggedMsg2 = mockDDLog.loggedMessages[$0.1 + 1]
  145. XCTAssertFalse(loggedMsg2.async)
  146. XCTAssertEqual(loggedMsg2.message.message, "\(level)-msg-with-sync")
  147. XCTAssertEqual(loggedMsg2.message.level, level.ddLogLevelAndFlag.0)
  148. XCTAssertEqual(loggedMsg2.message.flag, level.ddLogLevelAndFlag.1)
  149. XCTAssertEqual(loggedMsg2.message.file, #fileID)
  150. XCTAssertEqual(loggedMsg2.message.function, #function)
  151. XCTAssertEqual(loggedMsg2.message.line, logLine2)
  152. XCTAssertNotNil(loggedMsg2.message.swiftLogInfo)
  153. XCTAssertEqual(loggedMsg2.message.swiftLogInfo, .init(logger: .init(label: logger.label,
  154. metadataSources: .init(logger: logger.handler.metadata,
  155. provider: logger.metadataProvider?.get())),
  156. message: .init(message: "\(level)-msg-with-sync",
  157. level: level,
  158. metadata: message2Meta,
  159. source: logSource)))
  160. }
  161. }
  162. }