Keep private information out of your logs with Swift

One simple trick to avoid unintentionally leaking your users private info

struct User {
var identifier: String
var handle: String
var name: String
var dateOfBirth: Date
var city: String
}
func logError(_ description: String, userInfo: Any...) {
// ...
}
logError("trying to edit the user that is not current user")
logError("trying to edit the user that is not current user", user, currentUser)

…Well, there’s a problem

Open your logs, and here’s what you’re likely to see alongside your error messages:

▿ Your_App.User
- identifier: "6EEBA88F-D397-4108-B3D9-2B60E5FB4477"
- handle: "swifty15"
- name: "John Appleseed"
▿ dateOfBirth: 1976-01-04 20:36:07 +0000
- timeIntervalSinceReferenceDate: -788671432.408519
- city: "Oakland"

Don’t commit your users’ personal information to your logs!

But how, you would ask? Should I go over every single logError line in my code and make sure that I only submit the data I need? Should I now worry about it every time I write another log statement? How am I gonna make sure my teammates don’t make the same mistake?

Solution

Luckily, there’s a simple universal solution that can help you solve this problem once and for all. And it involves… 🥁

@propertyWrapper
struct LoggingExcluded<Value>: CustomStringConvertible, CustomDebugStringConvertible, CustomLeafReflectable {

var wrappedValue: Value

init(wrappedValue: Value) {
self.wrappedValue = wrappedValue
}

var description: String {
return "--redacted--"
}

var debugDescription: String {
return "--redacted--"
}

var customMirror: Mirror {
return Mirror(reflecting: "--redacted--")
}
}
struct User {
var identifier: String
var handle: String
@LoggingExcluded var name: String
@LoggingExcluded var dateOfBirth: Date
@LoggingExcluded var city: String
}
// print
User(identifier: "6EEBA88F-D397-4108-B3D9-2B60E5FB4477", handle: "swifty15", _name: --redacted--, _dateOfBirth: --redacted--, _city: --redacted--)
// debugPrint
Your_App.User(identifier: "6EEBA88F-D397-4108-B3D9-2B60E5FB4477", handle: "swifty15", _name: --redacted--, _dateOfBirth: --redacted--, _city: --redacted--)
// dump
▿ Your_App.User
- identifier: "6EEBA88F-D397-4108-B3D9-2B60E5FB4477"
- handle: "swifty15"
- _name: --redacted--
- _dateOfBirth: --redacted--
- _city: --redacted--

Appendix 1: Adding Codable conformance

extension LoggingExcluded: Decodable where Value: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
self.wrappedValue = try container.decode(Value.self)
}
}
extension LoggingExcluded: Encodable where Value: Encodable {
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(wrappedValue)
}
}

Further learning:

--

--

iOS development know-it-all. Talk to me about Swift, coffee, photography & motorsports.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Oleg Dreyman

iOS development know-it-all. Talk to me about Swift, coffee, photography & motorsports.