构建自定义记录器:自定义 Swift 所有类型的字符串插值
Building a custom logger: Custom Swift String interpolation for all types
我想重现新的 Swift 日志记录功能的行为,其中字符串中的内插值显示为 <private>
而不是我在我的自定义记录器中使用的实际值应用
例如:
let accountNumber = 12345
log("User account number: \(accountNumber)")
// User account number: 12345 ← leaking personal information
相反,我想要这样的结果:
// User account number: <private>
我查看了扩展 String.StringInterpolation
,它非常适合自定义类型,但我不知道如何连接到默认插值,因为我需要捕获任何插值类型。
这是一个带有自定义参数名称的示例,但很容易忘记指定:
extension String.StringInterpolation
{
mutating func appendInterpolation<T>(private value:T) {
let literal = "<private>"
appendLiteral(literal)
}
}
log("User account number: \(private: accountNumber)") // Too easy to forget "private:"...
// User account number: <private>
您可以创建一个自定义类型 - 让我们称它为 SecureMessage
- 即 ExpressibleByStringInterpolation
(和 CustomStringConvertible
更好的衡量标准)并让您的 log
函数接受它作为其消息参数:
func log(_ message: SecureMessage) {
print("\(message)")
}
let accountNumber = 12345
log("User account number: \(accountNumber)")
现在,我们可以定义SecureMessage
:
struct SecureMessage: ExpressibleByStringInterpolation,
CustomStringConvertible {
struct StringInterpolation: StringInterpolationProtocol {
var output = ""
init(literalCapacity: Int, interpolationCount: Int) { }
mutating func appendLiteral(_ literal: String) {
output.append(literal)
}
mutating func appendInterpolation<T>(_ str: T) {
output.append("<private>")
}
}
let description: String
init(stringLiteral value: String) {
description = value
}
init(stringInterpolation: StringInterpolation) {
description = stringInterpolation.output
}
}
当然,这有点没用,因为它会隐藏所有插值,而不仅仅是您认为的私有插值。
因此,您或许可以定义一个隐私级别,以便在每次插值时应用它:
enum PrivacyLevel {
case `public`, `private`
}
并更改 appendInterpolation
方法以获取具有某些默认隐私值的隐私参数:
mutating func appendInterpolation<T: LosslessStringConvertible>(_ str: T, privacy: PrivacyLevel = .private) {
switch privacy {
case .private:
output.append("<private>")
case .public:
output.append(String(str))
}
}
log("Hidden account number: \(accountNumber)") // private by default
log("Visible account number: \(accountNumber, privacy: .public)")
我想重现新的 Swift 日志记录功能的行为,其中字符串中的内插值显示为 <private>
而不是我在我的自定义记录器中使用的实际值应用
例如:
let accountNumber = 12345
log("User account number: \(accountNumber)")
// User account number: 12345 ← leaking personal information
相反,我想要这样的结果:
// User account number: <private>
我查看了扩展 String.StringInterpolation
,它非常适合自定义类型,但我不知道如何连接到默认插值,因为我需要捕获任何插值类型。
这是一个带有自定义参数名称的示例,但很容易忘记指定:
extension String.StringInterpolation
{
mutating func appendInterpolation<T>(private value:T) {
let literal = "<private>"
appendLiteral(literal)
}
}
log("User account number: \(private: accountNumber)") // Too easy to forget "private:"...
// User account number: <private>
您可以创建一个自定义类型 - 让我们称它为 SecureMessage
- 即 ExpressibleByStringInterpolation
(和 CustomStringConvertible
更好的衡量标准)并让您的 log
函数接受它作为其消息参数:
func log(_ message: SecureMessage) {
print("\(message)")
}
let accountNumber = 12345
log("User account number: \(accountNumber)")
现在,我们可以定义SecureMessage
:
struct SecureMessage: ExpressibleByStringInterpolation,
CustomStringConvertible {
struct StringInterpolation: StringInterpolationProtocol {
var output = ""
init(literalCapacity: Int, interpolationCount: Int) { }
mutating func appendLiteral(_ literal: String) {
output.append(literal)
}
mutating func appendInterpolation<T>(_ str: T) {
output.append("<private>")
}
}
let description: String
init(stringLiteral value: String) {
description = value
}
init(stringInterpolation: StringInterpolation) {
description = stringInterpolation.output
}
}
当然,这有点没用,因为它会隐藏所有插值,而不仅仅是您认为的私有插值。
因此,您或许可以定义一个隐私级别,以便在每次插值时应用它:
enum PrivacyLevel {
case `public`, `private`
}
并更改 appendInterpolation
方法以获取具有某些默认隐私值的隐私参数:
mutating func appendInterpolation<T: LosslessStringConvertible>(_ str: T, privacy: PrivacyLevel = .private) {
switch privacy {
case .private:
output.append("<private>")
case .public:
output.append(String(str))
}
}
log("Hidden account number: \(accountNumber)") // private by default
log("Visible account number: \(accountNumber, privacy: .public)")