iOS日志框架CocoaLumberjack

iOS日志框架CocoaLumberjack

========================
我使用了如下代码进行配置,只需在AppDelegate或SceneDelegate调用configDDLog即可(在其他某些文件配置也是可以的)

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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
//
// configDDLog.swift
// RunInto
//
// Created by 张赛东(手机:15674119605) on 2021/4/10.
// Copyright © 2021 adong666666. All rights reserved.
//

import Foundation
import CocoaLumberjack
/**
配置DDLog相关参数
*/
func configDDLog() {
// //让日志只在debug时输出
// #if DEBUG
// defaultDebugLevel = .Verbose
// #else
// dynamicLogLevel = .off
// #endif
//添加发送日志语句到苹果的日志系统
DDLog.add(DDOSLogger.sharedInstance)
//添加发送日志语句到Xcode控制台
//DDLog.add(DDTTYLogger.sharedInstance!)

//MARK: - 日志将会被保存到沙盒/Library/Caches/Logs,这意味着可以上传至服务器
let fileLogger: DDFileLogger = DDFileLogger() // File Logger
fileLogger.rollingFrequency = 60 * 60 * 24 // 24 hours
fileLogger.logFileManager.maximumNumberOfLogFiles = 7
DDLog.add(fileLogger)
log(fileLogger.currentLogFileInfo?.filePath)

// //允许控制台带颜色
// DDTTYLogger.sharedInstance?.colorsEnabled = true
// //设置Info下为蓝色
// DDTTYLogger.sharedInstance?.setForegroundColor(UIColor.blue, backgroundColor: UIColor.white, for: .info)
}

/**
得到输出的字符串的格式

- parameter message: 日志消息的主题
- parameter file: 日志消息所在的文件,方便调试定位用
- parameter function: 日志消息所在的方法,方便调试定位用
- parameter line: 日志消息所在的方法中的行数,方便调试定位用

- returns: 返回输出的日志字符串
*/
private func getMessage(message: String, file: StaticString, function: StaticString, line: UInt ) -> String {
//初始化需要返回的字符串
var returnMessage: String = ""
//通过file获取文件的名称
if let className = file.description.components(separatedBy: CharacterSet.init(charactersIn: "/")).last {
//拼接字符串
returnMessage = "\n" +
"className:\(className)\n" +
" function:\(function)\n" +
" ine:\(line)\n" +
" message:\(message)"
} else {
//拼接字符串
returnMessage = "\n" +
" function:\(function)\n" +
" ine:\(line)\n" +
" message:\(message)"
}
return returnMessage
}

/**
输出Info等级的日志消息
*/
public func logInfo(_ message: String, file: StaticString = #file, function: StaticString = #function, line: UInt = #line) {
DDLogInfo(getMessage(message: message, file: file, function: function, line: line))
}

/**
输出Error等级的日志消息
*/
public func logError(_ message: String, file: StaticString = #file, function: StaticString = #function, line: UInt = #line) {
DDLogError(getMessage(message: message, file: file, function: function, line: line))
}

/**
输出Debug等级的日志消息
*/
public func logDebug(_ message: String, file: StaticString = #file, function: StaticString = #function, line: UInt = #line) {
DDLogDebug(getMessage(message: message, file: file, function: function, line: line))
}

/**
输出Warn等级的日志消息
*/
public func logWarn(_ message: String, file: StaticString = #file, function: StaticString = #function, line: UInt = #line) {
DDLogWarn(getMessage(message: message, file: file, function: function, line: line))
}

public func logVerbose(_ message: String, file: StaticString = #file, function: StaticString = #function, line: UInt = #line) {
DDLogVerbose(getMessage(message: message, file: file, function: function, line: line))
}

此外,我还引用了官方demo的一些代码

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
// Software License Agreement (BSD License)
//
// Copyright (c) 2010-2021, Deusty, LLC
// All rights reserved.
//
// Redistribution and use of this software in source and binary forms,
// with or without modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Neither the name of Deusty nor the names of its contributors may be used
// to endorse or promote products derived from this software without specific
// prior written permission of Deusty, LLC.

#if SWIFT_PACKAGE
import CocoaLumberjack
import CocoaLumberjackSwiftSupport
#endif

/**
* Replacement for Swift's `assert` function that will output a log message even when assertions
* are disabled.
*
* - Parameters:
* - condition: The condition to test. Unlike `Swift.assert`, `condition` is always evaluated,
* even when assertions are disabled.
* - message: A string to log (using `DDLogError`) if `condition` evaluates to `false`.
* The default is an empty string.
*/
@inlinable
public func DDAssert(_ condition: @autoclosure () -> Bool, _ message: @autoclosure () -> String = "", level: DDLogLevel = .all, context: Int = 0, file: StaticString = #file, function: StaticString = #function, line: UInt = #line, tag: Any? = nil, asynchronous async: Bool = false, ddlog: DDLog = DDLog.sharedInstance) {
if !condition() {
DDLogError(message(), level: level, context: context, file: file, function: function, line: line, tag: tag, asynchronous: async, ddlog: ddlog)
Swift.assertionFailure(message(), file: file, line: line)
}
}

/**
* Replacement for Swift's `assertionFailure` function that will output a log message even
* when assertions are disabled.
*
* - Parameters:
* - message: A string to log (using `DDLogError`). The default is an empty string.
*/
@inlinable
public func DDAssertionFailure(_ message: @autoclosure () -> String = "", level: DDLogLevel = .all, context: Int = 0, file: StaticString = #file, function: StaticString = #function, line: UInt = #line, tag: Any? = nil, asynchronous async: Bool = false, ddlog: DDLog = DDLog.sharedInstance) {
DDLogError(message(), level: level, context: context, file: file, function: function, line: line, tag: tag, asynchronous: async, ddlog: ddlog)
Swift.assertionFailure(message(), file: file, line: line)
}
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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
// Software License Agreement (BSD License)
//
// Copyright (c) 2010-2021, Deusty, LLC
// All rights reserved.
//
// Redistribution and use of this software in source and binary forms,
// with or without modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Neither the name of Deusty nor the names of its contributors may be used
// to endorse or promote products derived from this software without specific
// prior written permission of Deusty, LLC.

@_exported import CocoaLumberjack
#if SWIFT_PACKAGE
import CocoaLumberjackSwiftSupport
#endif

extension DDLogFlag {
public static func from(_ logLevel: DDLogLevel) -> DDLogFlag {
return DDLogFlag(rawValue: logLevel.rawValue)
}

public init(_ logLevel: DDLogLevel) {
self = DDLogFlag(rawValue: logLevel.rawValue)
}

/// Returns the log level, or the lowest equivalent.
public func toLogLevel() -> DDLogLevel {
if let ourValid = DDLogLevel(rawValue: rawValue) {
return ourValid
} else {
if contains(.verbose) {
return .verbose
} else if contains(.debug) {
return .debug
} else if contains(.info) {
return .info
} else if contains(.warning) {
return .warning
} else if contains(.error) {
return .error
} else {
return .off
}
}
}
}

/// The log level that can dynamically limit log messages (vs. the static DDDefaultLogLevel). This log level will only be checked, if the message passes the `DDDefaultLogLevel`.
public var dynamicLogLevel = DDLogLevel.all

/// Resets the `dynamicLogLevel` to `.all`.
/// - SeeAlso: `dynamicLogLevel`
@inlinable
public func resetDynamicLogLevel() {
dynamicLogLevel = .all
}

@available(*, deprecated, message: "Please use dynamicLogLevel", renamed: "dynamicLogLevel")
public var defaultDebugLevel: DDLogLevel {
get {
return dynamicLogLevel
}
set {
dynamicLogLevel = newValue
}
}

@available(*, deprecated, message: "Please use resetDynamicLogLevel", renamed: "resetDynamicLogLevel")
public func resetDefaultDebugLevel() {
resetDynamicLogLevel()
}

/// If `true`, all logs (except errors) are logged asynchronously by default.
public var asyncLoggingEnabled = true

@inlinable
public func _DDLogMessage(_ message: @autoclosure () -> Any,
level: DDLogLevel,
flag: DDLogFlag,
context: Int,
file: StaticString,
function: StaticString,
line: UInt,
tag: Any?,
asynchronous: Bool,
ddlog: DDLog) {
// The `dynamicLogLevel` will always be checked here (instead of being passed in).
// We cannot "mix" it with the `DDDefaultLogLevel`, because otherwise the compiler won't strip strings that are not logged.
if level.rawValue & flag.rawValue != 0 && dynamicLogLevel.rawValue & flag.rawValue != 0 {
// Tell the DDLogMessage constructor to copy the C strings that get passed to it.
let logMessage = DDLogMessage(message: String(describing: message()),
level: level,
flag: flag,
context: context,
file: String(describing: file),
function: String(describing: function),
line: line,
tag: tag,
options: [.copyFile, .copyFunction],
timestamp: nil)
ddlog.log(asynchronous: asynchronous, message: logMessage)
}
}

@inlinable
public func DDLogDebug(_ message: @autoclosure () -> Any,
level: DDLogLevel = .debug,
context: Int = 0,
file: StaticString = #file,
function: StaticString = #function,
line: UInt = #line,
tag: Any? = nil,
asynchronous async: Bool = asyncLoggingEnabled,
ddlog: DDLog = .sharedInstance) {
_DDLogMessage(message(), level: level, flag: .debug, context: context, file: file, function: function, line: line, tag: tag, asynchronous: async, ddlog: ddlog)
}

@inlinable
public func DDLogInfo(_ message: @autoclosure () -> Any,
level: DDLogLevel = .info,
context: Int = 0,
file: StaticString = #file,
function: StaticString = #function,
line: UInt = #line,
tag: Any? = nil,
asynchronous async: Bool = asyncLoggingEnabled,
ddlog: DDLog = .sharedInstance) {
_DDLogMessage(message(), level: level, flag: .info, context: context, file: file, function: function, line: line, tag: tag, asynchronous: async, ddlog: ddlog)
}

@inlinable
public func DDLogWarn(_ message: @autoclosure () -> Any,
level: DDLogLevel = DDLogLevel.warning,
context: Int = 0,
file: StaticString = #file,
function: StaticString = #function,
line: UInt = #line,
tag: Any? = nil,
asynchronous async: Bool = asyncLoggingEnabled,
ddlog: DDLog = .sharedInstance) {
_DDLogMessage(message(), level: level, flag: .warning, context: context, file: file, function: function, line: line, tag: tag, asynchronous: async, ddlog: ddlog)
}

@inlinable
public func DDLogVerbose(_ message: @autoclosure () -> Any,
level: DDLogLevel = .verbose,
context: Int = 0,
file: StaticString = #file,
function: StaticString = #function,
line: UInt = #line,
tag: Any? = nil,
asynchronous async: Bool = asyncLoggingEnabled,
ddlog: DDLog = .sharedInstance) {
_DDLogMessage(message(), level: level, flag: .verbose, context: context, file: file, function: function, line: line, tag: tag, asynchronous: async, ddlog: ddlog)
}

@inlinable
public func DDLogError(_ message: @autoclosure () -> Any,
level: DDLogLevel = .error,
context: Int = 0,
file: StaticString = #file,
function: StaticString = #function,
line: UInt = #line,
tag: Any? = nil,
asynchronous async: Bool = false,
ddlog: DDLog = .sharedInstance) {
_DDLogMessage(message(), level: level, flag: .error, context: context, file: file, function: function, line: line, tag: tag, asynchronous: async, ddlog: ddlog)
}

/// Returns a String of the current filename, without full path or extension.
///
/// Analogous to the C preprocessor macro `THIS_FILE`.
public func currentFileName(_ fileName: StaticString = #file) -> String {
var str = String(describing: fileName)
if let idx = str.range(of: "/", options: .backwards)?.upperBound {
str = String(str[idx...])
}
if let idx = str.range(of: ".", options: .backwards)?.lowerBound {
str = String(str[..<idx])
}
return str
}

// swiftlint:disable identifier_name
// swiftlint doesn't like func names that begin with a capital letter - deprecated
@available(*, deprecated, message: "Please use currentFileName", renamed: "currentFileName")
public func CurrentFileName(_ fileName: StaticString = #file) -> String {
return currentFileName(fileName)
}

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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
// Software License Agreement (BSD License)
//
// Copyright (c) 2010-2021, Deusty, LLC
// All rights reserved.
//
// Redistribution and use of this software in source and binary forms,
// with or without modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Neither the name of Deusty nor the names of its contributors may be used
// to endorse or promote products derived from this software without specific
// prior written permission of Deusty, LLC.

#if canImport(Combine)

import Combine

#if SWIFT_PACKAGE
import CocoaLumberjack
import CocoaLumberjackSwiftSupport
#endif

@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
extension DDLog {

/**
* Creates a message publisher.
*
* The publisher will add and remove loggers as subscriptions are added and removed.
*
* The level that you provide here is a preemptive filter (for performance).
* That is, the level specified here will be used to filter out logMessages so that
* the logger is never even invoked for the messages.
*
* More information:
* See -[DDLog addLogger:with:]
*
* - Parameter logLevel: preemptive filter of the message returned by the publisher. All levels are sent by default
* - Returns: A MessagePublisher that emits LogMessages filtered by the specified logLevel
**/
public func messagePublisher(with logLevel: DDLogLevel = .all) -> MessagePublisher {
return MessagePublisher(log: self, with: logLevel)
}

// MARK: - MessagePublisher

@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
public struct MessagePublisher: Combine.Publisher {

public typealias Output = DDLogMessage
public typealias Failure = Never

private let log: DDLog
private let logLevel: DDLogLevel

public init(log: DDLog, with logLevel: DDLogLevel) {
self.log = log
self.logLevel = logLevel
}

public func receive<S>(subscriber: S) where S: Subscriber, S.Failure == Failure, S.Input == Output {

let subscription = Subscription(log: self.log, with: logLevel, subscriber: subscriber)
subscriber.receive(subscription: subscription)
}
}

// MARK: - Subscription

private final class Subscription<S: Subscriber>: NSObject, DDLogger, Combine.Subscription
where S.Input == DDLogMessage {
private var subscriber: S?
private weak var log: DDLog?

//Not used but DDLogger requires it. The preferred way to achieve this is to use the `map()` Combine operator of the publisher.
//ie:
// DDLog.sharedInstance.messagePublisher()
// .map { [format log message] }
// .sink(receiveValue: { [process log message] })
//
var logFormatter: DDLogFormatter? = nil

let combineIdentifier = CombineIdentifier()

init(log: DDLog, with logLevel: DDLogLevel, subscriber: S) {

self.subscriber = subscriber
self.log = log

super.init()

log.add(self, with: logLevel)
}

func request(_ demand: Subscribers.Demand) {
//The log messages are endless until canceled, so we won't do any demand management.
//Combine operators can be used to deal with it as needed.
}

func cancel() {
self.log?.remove(self)
self.subscriber = nil
}

func log(message logMessage: DDLogMessage) {
_ = self.subscriber?.receive(logMessage)
}
}
}

@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
extension Publisher where Output == DDLogMessage {

public func formatted(with formatter: DDLogFormatter) -> Publishers.CompactMap<Self, String> {
return compactMap { formatter.format(message: $0) }
}
}

#endif