Skip to content

Commit 7ca9270

Browse files
authored
Make Logger thread-safe (#15)
* Make Logger thread-safe * Fix file header and add template * Reduce to one lock
1 parent 9f96552 commit 7ca9270

File tree

3 files changed

+359
-89
lines changed

3 files changed

+359
-89
lines changed

Sources/StreamCore/Logger/Logger.swift

Lines changed: 206 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -130,173 +130,290 @@ public struct LogSubsystem: OptionSet, CustomStringConvertible, Sendable {
130130
}
131131

132132
public enum LogConfig {
133+
private struct State {
134+
var identifier: String = ""
135+
var level: LogLevel = .error
136+
var dateFormatter: DateFormatter = {
137+
let df = DateFormatter()
138+
df.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS"
139+
return df
140+
}()
141+
142+
var formatters: [LogFormatter] = []
143+
var showDate: Bool = true
144+
var showLevel: Bool = true
145+
var showIdentifier: Bool = false
146+
var showThreadName: Bool = true
147+
var showFileName: Bool = true
148+
var showLineNumber: Bool = true
149+
var showFunctionName: Bool = true
150+
var subsystems: LogSubsystem = .all
151+
152+
var destinationTypes: [LogDestination.Type] = if #available(iOS 14.0, *) {
153+
[OSLogDestination.self]
154+
} else {
155+
[ConsoleLogDestination.self]
156+
}
157+
158+
private var _destinations: [LogDestination]?
159+
160+
var destinations: [LogDestination] {
161+
mutating get {
162+
if let _destinations {
163+
return _destinations
164+
}
165+
let newDestinations = destinationTypes.map {
166+
$0.init(
167+
identifier: identifier,
168+
level: level,
169+
subsystems: subsystems,
170+
showDate: showDate,
171+
dateFormatter: dateFormatter,
172+
formatters: formatters,
173+
showLevel: showLevel,
174+
showIdentifier: showIdentifier,
175+
showThreadName: showThreadName,
176+
showFileName: showFileName,
177+
showLineNumber: showLineNumber,
178+
showFunctionName: showFunctionName
179+
)
180+
}
181+
_destinations = newDestinations
182+
return newDestinations
183+
}
184+
set {
185+
_destinations = newValue
186+
}
187+
}
188+
189+
private var _logger: Logger?
190+
191+
var logger: Logger {
192+
mutating get {
193+
if let _logger {
194+
return _logger
195+
}
196+
let logger = Logger(identifier: identifier, destinations: destinations)
197+
_logger = logger
198+
return logger
199+
}
200+
set {
201+
_logger = newValue
202+
}
203+
}
204+
205+
mutating func invalidateLogger() {
206+
_destinations = nil
207+
_logger = nil
208+
}
209+
}
210+
211+
private static let _state = AllocatedUnfairLock<State>(State())
212+
133213
/// Identifier for the logger. Defaults to empty.
134-
public nonisolated(unsafe) static var identifier = "" {
135-
didSet {
136-
invalidateLogger()
214+
public static var identifier: String {
215+
get {
216+
_state.withLock { $0.identifier }
217+
}
218+
set {
219+
_state.withLock {
220+
$0.identifier = newValue
221+
$0.invalidateLogger()
222+
}
137223
}
138224
}
139225

140226
/// Output level for the logger.
141-
public nonisolated(unsafe) static var level: LogLevel = .error {
142-
didSet {
143-
invalidateLogger()
227+
public static var level: LogLevel {
228+
get {
229+
_state.withLock { $0.level }
230+
}
231+
set {
232+
_state.withLock {
233+
$0.level = newValue
234+
$0.invalidateLogger()
235+
}
144236
}
145237
}
146238

147239
/// Date formatter for the logger. Defaults to ISO8601
148-
public nonisolated(unsafe) static var dateFormatter: DateFormatter = {
149-
let df = DateFormatter()
150-
df.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS"
151-
return df
152-
}() {
153-
didSet {
154-
invalidateLogger()
240+
public static var dateFormatter: DateFormatter {
241+
get {
242+
_state.withLock { $0.dateFormatter }
243+
}
244+
set {
245+
_state.withLock {
246+
$0.dateFormatter = newValue
247+
$0.invalidateLogger()
248+
}
155249
}
156250
}
157251

158252
/// Log formatters to be applied in order before logs are outputted. Defaults to empty (no formatters).
159253
/// Please see `LogFormatter` for more info.
160-
public nonisolated(unsafe) static var formatters = [LogFormatter]() {
161-
didSet {
162-
invalidateLogger()
254+
public static var formatters: [LogFormatter] {
255+
get {
256+
_state.withLock { $0.formatters }
257+
}
258+
set {
259+
_state.withLock {
260+
$0.formatters = newValue
261+
$0.invalidateLogger()
262+
}
163263
}
164264
}
165265

166266
/// Toggle for showing date in logs
167-
public nonisolated(unsafe) static var showDate = true {
168-
didSet {
169-
invalidateLogger()
267+
public static var showDate: Bool {
268+
get {
269+
_state.withLock { $0.showDate }
270+
}
271+
set {
272+
_state.withLock {
273+
$0.showDate = newValue
274+
$0.invalidateLogger()
275+
}
170276
}
171277
}
172278

173279
/// Toggle for showing log level in logs
174-
public nonisolated(unsafe) static var showLevel = true {
175-
didSet {
176-
invalidateLogger()
280+
public static var showLevel: Bool {
281+
get {
282+
_state.withLock { $0.showLevel }
283+
}
284+
set {
285+
_state.withLock {
286+
$0.showLevel = newValue
287+
$0.invalidateLogger()
288+
}
177289
}
178290
}
179291

180292
/// Toggle for showing identifier in logs
181-
public nonisolated(unsafe) static var showIdentifier = false {
182-
didSet {
183-
invalidateLogger()
293+
public static var showIdentifier: Bool {
294+
get {
295+
_state.withLock { $0.showIdentifier }
296+
}
297+
set {
298+
_state.withLock {
299+
$0.showIdentifier = newValue
300+
$0.invalidateLogger()
301+
}
184302
}
185303
}
186304

187305
/// Toggle for showing thread name in logs
188-
public nonisolated(unsafe) static var showThreadName = true {
189-
didSet {
190-
invalidateLogger()
306+
public static var showThreadName: Bool {
307+
get {
308+
_state.withLock { $0.showThreadName }
309+
}
310+
set {
311+
_state.withLock {
312+
$0.showThreadName = newValue
313+
$0.invalidateLogger()
314+
}
191315
}
192316
}
193317

194318
/// Toggle for showing file name in logs
195-
public nonisolated(unsafe) static var showFileName = true {
196-
didSet {
197-
invalidateLogger()
319+
public static var showFileName: Bool {
320+
get {
321+
_state.withLock { $0.showFileName }
322+
}
323+
set {
324+
_state.withLock {
325+
$0.showFileName = newValue
326+
$0.invalidateLogger()
327+
}
198328
}
199329
}
200330

201331
/// Toggle for showing line number in logs
202-
public nonisolated(unsafe) static var showLineNumber = true {
203-
didSet {
204-
invalidateLogger()
332+
public static var showLineNumber: Bool {
333+
get {
334+
_state.withLock { $0.showLineNumber }
335+
}
336+
set {
337+
_state.withLock {
338+
$0.showLineNumber = newValue
339+
$0.invalidateLogger()
340+
}
205341
}
206342
}
207343

208344
/// Toggle for showing function name in logs
209-
public nonisolated(unsafe) static var showFunctionName = true {
210-
didSet {
211-
invalidateLogger()
345+
public static var showFunctionName: Bool {
346+
get {
347+
_state.withLock { $0.showFunctionName }
348+
}
349+
set {
350+
_state.withLock {
351+
$0.showFunctionName = newValue
352+
$0.invalidateLogger()
353+
}
212354
}
213355
}
214356

215357
/// Subsystems for the logger
216-
public nonisolated(unsafe) static var subsystems: LogSubsystem = .all {
217-
didSet {
218-
invalidateLogger()
358+
public static var subsystems: LogSubsystem {
359+
get {
360+
_state.withLock { $0.subsystems }
361+
}
362+
set {
363+
_state.withLock {
364+
$0.subsystems = newValue
365+
$0.invalidateLogger()
366+
}
219367
}
220368
}
221369

222370
/// Destination types this logger will use.
223371
///
224372
/// Logger will initialize the destinations with its own parameters. If you want full control on the parameters, use `destinations` directly,
225373
/// where you can pass parameters to destination initializers yourself.
226-
public nonisolated(unsafe) static var destinationTypes: [LogDestination.Type] = Self.defaultDestinations {
227-
didSet {
228-
invalidateLogger()
374+
public static var destinationTypes: [LogDestination.Type] {
375+
get {
376+
_state.withLock { $0.destinationTypes }
229377
}
230-
}
231-
232-
static var defaultDestinations: [LogDestination.Type] {
233-
if #available(iOS 14.0, *) {
234-
[OSLogDestination.self]
235-
} else {
236-
[ConsoleLogDestination.self]
378+
set {
379+
_state.withLock {
380+
$0.destinationTypes = newValue
381+
$0.invalidateLogger()
382+
}
237383
}
238384
}
239-
240-
private nonisolated(unsafe) static var _destinations: [LogDestination]?
241385

242386
/// Destinations for the default logger. Please see `LogDestination`.
243387
/// Defaults to only `ConsoleLogDestination`, which only prints the messages.
244388
///
245389
/// - Important: Other options in `ChatClientConfig.Logging` will not take affect if this is changed.
246390
public static var destinations: [LogDestination] {
247391
get {
248-
if let destinations = _destinations {
249-
return destinations
250-
} else {
251-
_destinations = destinationTypes.map {
252-
$0.init(
253-
identifier: identifier,
254-
level: level,
255-
subsystems: subsystems,
256-
showDate: showDate,
257-
dateFormatter: dateFormatter,
258-
formatters: formatters,
259-
showLevel: showLevel,
260-
showIdentifier: showIdentifier,
261-
showThreadName: showThreadName,
262-
showFileName: showFileName,
263-
showLineNumber: showLineNumber,
264-
showFunctionName: showFunctionName
265-
)
266-
}
267-
return _destinations!
268-
}
392+
_state.withLock { $0.destinations }
269393
}
270394
set {
271-
invalidateLogger()
272-
_destinations = newValue
395+
_state.withLock {
396+
// Order is important
397+
$0.invalidateLogger()
398+
$0.destinations = newValue
399+
}
273400
}
274401
}
275-
276-
/// Underlying logger instance to control singleton.
277-
private nonisolated(unsafe) static var _logger: Logger?
278402

279403
/// Logger instance to be used by StreamChat.
280404
///
281405
/// - Important: Other options in `LogConfig` will not take affect if this is changed.
282406
public static var logger: Logger {
283407
get {
284-
if let logger = _logger {
285-
return logger
286-
} else {
287-
_logger = Logger(identifier: identifier, destinations: destinations)
288-
return _logger!
289-
}
408+
_state.withLock { $0.logger }
290409
}
291410
set {
292-
_logger = newValue
411+
_state.withLock { $0.logger = newValue }
293412
}
294413
}
295414

296-
/// Invalidates the current logger instance so it can be recreated.
297-
private static func invalidateLogger() {
298-
_logger = nil
299-
_destinations = nil
415+
static func reset() {
416+
_state.withLock { $0 = State() }
300417
}
301418
}
302419

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>FILEHEADER</key>
6+
<string>
7+
// Copyright © ___YEAR___ Stream.io Inc. All rights reserved.
8+
//</string>
9+
</dict>
10+
</plist>

0 commit comments

Comments
 (0)