-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
339 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- | ||
// Created by Sam Deane on 27/09/24. | ||
// All code (c) 2024 - present day, Elegant Chaos Limited. | ||
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- | ||
|
||
public actor BasicHandler: Handler { | ||
public typealias Logger = @Sendable (Sendable, Context, BasicHandler) async -> Void | ||
|
||
public let name: String | ||
let showName: Bool | ||
let showSubsystem: Bool | ||
let logger: Logger | ||
|
||
public init( | ||
_ name: String, showName: Bool = true, showSubsystem: Bool = false, logger: @escaping Logger | ||
) { | ||
self.name = name | ||
self.showName = showName | ||
self.showSubsystem = showSubsystem | ||
self.logger = logger | ||
} | ||
|
||
/// Log something. | ||
public func log(_ value: Sendable, context: Context) async { await logger(value, context, self) } | ||
|
||
/// Calculate a text tag indicating the context. | ||
/// Provided as a utility for logger callbacks to use as they need. | ||
internal func tag(for context: Context) -> String { | ||
let channel = context.channel | ||
if showName, showSubsystem { | ||
return "[\(channel.subsystem).\(channel.name)] " | ||
} else if showName { | ||
return "[\(channel.name)] " | ||
} else if showSubsystem { | ||
return "[\(channel.subsystem)] " | ||
} else { | ||
return "" | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- | ||
// Created by Sam Deane, 15/02/2018. | ||
// All code (c) 2018 - present day, Elegant Chaos Limited. | ||
// For licensing terms, see http://elegantchaos.com/license/liberal/. | ||
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- | ||
|
||
/// Something responsible for sending log output somewhere. | ||
/// Examples might include printing the output with NSLog/print/OSLog, | ||
/// writing it to disk, or sending it down a pipe or network stream. | ||
|
||
public protocol Handler: Sendable { | ||
var name: String { get } | ||
func log(_ value: Sendable, context: Context) async | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- | ||
// Created by Sam Deane on 27/09/24. | ||
// All code (c) 2024 - present day, Elegant Chaos Limited. | ||
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- | ||
|
||
#if os(macOS) || os(iOS) | ||
import Foundation | ||
|
||
/// Outputs log messages using NSLog() | ||
let nslogHandler = BasicHandler("nslog") { | ||
value, context, handler in | ||
let tag = handler.tag(for: context) | ||
NSLog("\(tag)\(value)") | ||
} | ||
|
||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- | ||
// Created by Sam Deane, 27/02/2018. | ||
// All code (c) 2018 - present day, Elegant Chaos Limited. | ||
// For licensing terms, see http://elegantchaos.com/license/liberal/. | ||
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- | ||
|
||
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) | ||
|
||
import os | ||
|
||
/// Outputs log messages using os_log(). | ||
public actor OSLogHandler: Handler { | ||
/// Table of logs - one for each channel. | ||
var logTable: [Channel: OSLog] = [:] | ||
public let name = "oslog" | ||
public func log(_ value: Sendable, context: Context) async { | ||
let channel = context.channel | ||
let log = logTable[ | ||
channel, default: OSLog(subsystem: channel.subsystem, category: channel.name)] | ||
|
||
let message = String(describing: value) | ||
let dso = UnsafeRawPointer(bitPattern: context.dso) | ||
os_log("%{public}@", dso: dso, log: log, type: .default, message) | ||
} | ||
|
||
public func run(_ stream: LogSequence) async { | ||
for await item in stream { | ||
let channel = item.context.channel | ||
let log = logTable[ | ||
channel, default: OSLog(subsystem: channel.subsystem, category: channel.name)] | ||
|
||
let message = String(describing: item.value) | ||
let dso = UnsafeRawPointer(bitPattern: item.context.dso) | ||
os_log("%{public}@", dso: dso, log: log, type: .default, message) | ||
|
||
} | ||
} | ||
} | ||
|
||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- | ||
// Created by Sam Deane on 27/09/24. | ||
// All code (c) 2024 - present day, Elegant Chaos Limited. | ||
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- | ||
|
||
/// Outputs log messages using swift's print() function. | ||
let printHandler = BasicHandler("print") { value, context, handler in | ||
let tag = handler.tag(for: context) | ||
print("\(tag)\(value)") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- | ||
// Created by Sam Deane on 27/09/24. | ||
// All code (c) 2024 - present day, Elegant Chaos Limited. | ||
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- | ||
|
||
public struct LoggedItem: Sendable { | ||
let value: Sendable | ||
let context: Context | ||
} | ||
|
||
/// An async stream of logged items. | ||
public typealias LogStream = AsyncThrowingStream<any Sendable, any Error> | ||
public typealias LogStream2 = AsyncStream<LoggedItem> | ||
|
||
/// Handler which outputs the logged items to an async stream. | ||
public actor StreamHandler: Handler { | ||
public init( | ||
_ name: String, | ||
continuation: AsyncThrowingStream<any Sendable, Error>.Continuation | ||
) { | ||
self.name = name | ||
self.continuation = continuation | ||
} | ||
|
||
/// Name of the stream. | ||
public let name: String | ||
|
||
/// Continuation to yield logged items to. | ||
let continuation: AsyncThrowingStream<Sendable, Error>.Continuation | ||
|
||
/// Log an item. | ||
public func log(_ value: Sendable, context: Context) async { | ||
print("logged \(value)") | ||
continuation.yield(value) | ||
} | ||
|
||
public func finish() { | ||
continuation.finish() | ||
} | ||
} | ||
|
||
public struct LogSequence: AsyncSequence, Sendable { | ||
public typealias AsyncIterator = LogStream2.Iterator | ||
public typealias Element = LoggedItem | ||
|
||
var stream: LogStream2! | ||
var continuation: LogStream2.Continuation! | ||
|
||
public init() { | ||
self.stream = LogStream2 { continuation in | ||
self.continuation = continuation | ||
} | ||
} | ||
|
||
public func makeAsyncIterator() -> LogStream2.Iterator { | ||
stream.makeAsyncIterator() | ||
} | ||
|
||
func log(_ item: LoggedItem) { | ||
continuation.yield(item) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- | ||
// Created by Sam Deane on 20/07/22. | ||
// All code (c) 2022 - present day, Elegant Chaos Limited. | ||
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- | ||
|
||
import Foundation | ||
|
||
/// Protocol for reading/writing settings store by the manager. | ||
/// | ||
/// This is generalised into a protocol mostly to make it easier to | ||
/// inject settings during testing. | ||
|
||
public protocol ManagerSettings { | ||
/// The identifiers of the channels that should be enabled. | ||
var enabledChannelIDs: Set<Channel.ID> { get } | ||
|
||
/// Store the identifiers of the channels that should be enabled. | ||
func saveEnabledChannelIDs(_ ids: Set<Channel.ID>) | ||
|
||
/// Strip any settings-related command line arguments. | ||
func removeLoggingOptions(fromCommandLineArguments arguments: [String]) -> [String] | ||
} | ||
|
||
extension ManagerSettings { | ||
/// Store the channels that should be enabled. | ||
public func saveEnabledChannels(_ channels: Manager.Channels) { | ||
saveEnabledChannelIDs(Set(channels.map(\.id))) | ||
} | ||
} |
128 changes: 128 additions & 0 deletions
128
Sources/Logger/Settings/UserDefaultsManagerSettings.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- | ||
// Created by Sam Deane on 20/07/22. | ||
// All code (c) 2022 - present day, Elegant Chaos Limited. | ||
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- | ||
|
||
import Foundation | ||
|
||
/// Implementation of ManagerSettings that uses the UserDefaults system. | ||
/// The default Manager.shared instance uses one of these to read/write | ||
/// settings, and in normal situations it should be all you need. | ||
|
||
struct UserDefaultsManagerSettings: ManagerSettings { | ||
let defaults: UserDefaults | ||
|
||
init(defaults: UserDefaults = UserDefaults.standard) { | ||
self.defaults = defaults | ||
setup() | ||
} | ||
|
||
/** | ||
Calculate the list of enabled channels. | ||
This is determined by two settings: `logsKey` and `persistentLogsKey`, | ||
both of which contain comma-delimited strings. | ||
The persistentLogs setting contains the names of all the channels that were | ||
enabled last time. This is expected to be read from the user defaults. | ||
The logs setting contains modifiers, and if present, is expected to have | ||
been supplied on the command line. | ||
*/ | ||
|
||
mutating func setup() { | ||
let existingChannels = enabledChannelIDs | ||
let modifiers = defaults.string(forKey: .logsKey) ?? "" | ||
let updatedChannels = Self.updateChannels(existingChannels, applyingModifiers: modifiers) | ||
|
||
// persist any changes for the next launch | ||
saveEnabledChannelIDs(updatedChannels) | ||
|
||
// we don't want the modifiers to persist between launches, so we clear them out after reading them | ||
defaults.set("", forKey: .logsKey) | ||
} | ||
|
||
/** | ||
Update a set of enabled channels, using a list of channel modifiers. | ||
Items in the modifiers can be in two forms: | ||
- "name1,-name2,+name3": *modifies* the list by enabling/disabling named channels | ||
- "=name1,name2,name3": *resets* the list to the named channels | ||
Note that `+name` is a synonym for `name` in the first form - there just for symmetry. | ||
Note also that if any item in the list begins with `=`, the second form is used and the list is reset. | ||
*/ | ||
|
||
static func updateChannels(_ channels: Set<Channel.ID>, applyingModifiers modifiers: String) -> Set<Channel.ID> { | ||
var updatedChannels = channels | ||
var onlyDeltas = true | ||
var newItems = Set<Channel.ID>() | ||
for item in modifiers.split(separator: ",").map({ $0.trimmingCharacters(in: .whitespaces) }) { | ||
if let first = item.first { | ||
switch first { | ||
case "=": | ||
newItems.insert(Channel.ID(item.dropFirst())) | ||
onlyDeltas = false | ||
case "-": | ||
updatedChannels.remove(Channel.ID(item.dropFirst())) | ||
case "+": | ||
newItems.insert(Channel.ID(item.dropFirst())) | ||
default: | ||
newItems.insert(Channel.ID(item)) | ||
} | ||
} | ||
} | ||
|
||
if onlyDeltas { | ||
updatedChannels.formUnion(newItems) | ||
} else { | ||
updatedChannels = newItems | ||
} | ||
|
||
return updatedChannels | ||
} | ||
|
||
var enabledChannelIDs: Set<Channel.ID> { | ||
let s = defaults.string(forKey: .persistentLogsKey)?.split(separator: ",").map { String($0) } | ||
return Set(s ?? []) | ||
} | ||
|
||
func saveEnabledChannelIDs(_ ids: Set<Channel.ID>) { | ||
let sortedIDs = ids.sorted().joined(separator: ",") | ||
defaults.set(sortedIDs, forKey: .persistentLogsKey) | ||
} | ||
|
||
/** | ||
Returns a copy of the input arguments array which has had any | ||
arguments that we handle stripped out of it. | ||
This is useful for clients that are parsing the command line arguments, | ||
particularly with something like Docopt. | ||
Our options are meant to be semi-hidden, and we don't really want every | ||
client of this library to have to know about all of them, or to have | ||
to document them. | ||
*/ | ||
|
||
public func removeLoggingOptions(fromCommandLineArguments arguments: [String]) -> [String] { | ||
let key: String = .logsKey | ||
var args: [String] = [] | ||
var dropNext = false | ||
for argument in arguments { | ||
if argument == "-\(key)" { | ||
dropNext = true | ||
} else if dropNext { | ||
dropNext = false | ||
} else if !argument.starts(with: "--\(key)=") { | ||
args.append(argument) | ||
} | ||
} | ||
return args | ||
} | ||
} | ||
|
||
extension String { | ||
static let persistentLogsKey = "logs-persistent" | ||
static let logsKey = "logs" | ||
} |