Skip to content

Commit 14243c5

Browse files
committed
[PlaygroundLogger] Introduced LogPolicy, with support for capping the number of children logged.
The defaults are to log up to the first 10,000 children of "aggregates" (i.e. classes, structs, etc.) and up to the first 80 + last 20 of "containers" (i.e. arrays, dictionaries, sets, etc.). This matches the behavior of the legacy PlaygroundLogger. This commit includes introducing new tests that these policies are applied correctly.
1 parent 0fa7101 commit 14243c5

File tree

7 files changed

+491
-31
lines changed

7 files changed

+491
-31
lines changed

PlaygroundLogger/PlaygroundLogger.xcodeproj/project.pbxproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@
7474
5E2756941FCF9CB000B69C83 /* SpriteKitOpaqueLoggable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E2756931FCF9CB000B69C83 /* SpriteKitOpaqueLoggable.swift */; };
7575
5E4AF1AD1FDDBDE400B7C9D9 /* Unimplemented.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E4AF1AC1FDDBDE400B7C9D9 /* Unimplemented.swift */; };
7676
5E4AF1AF1FDDC12A00B7C9D9 /* LegacyEntrypoints.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E4AF1AE1FDDC12A00B7C9D9 /* LegacyEntrypoints.swift */; };
77+
5E5D77802040BD7D00EBC3A9 /* LogPolicyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E5D777F2040BD7D00EBC3A9 /* LogPolicyTests.swift */; };
78+
5E5D77812040BD7D00EBC3A9 /* LogPolicyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E5D777F2040BD7D00EBC3A9 /* LogPolicyTests.swift */; };
79+
5E5D77822040BD7D00EBC3A9 /* LogPolicyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E5D777F2040BD7D00EBC3A9 /* LogPolicyTests.swift */; };
80+
5E5F600B20409D4E007EF0A8 /* LogPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E5F600A20409D4E007EF0A8 /* LogPolicy.swift */; };
7781
5E5FE50B202D13C800E28C3C /* PGLConcurrentMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E5FE50A202D13C800E28C3C /* PGLConcurrentMap.swift */; };
7882
5ECE8F911FFCD2A70034D9BC /* LegacyPlaygroundLoggerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ECE8F901FFCD2A70034D9BC /* LegacyPlaygroundLoggerTests.swift */; };
7983
5EE3867420352F3200D625F0 /* CGFloat+CustomOpaqueLoggable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EE3867320352F3200D625F0 /* CGFloat+CustomOpaqueLoggable.swift */; };
@@ -238,6 +242,8 @@
238242
5E2756931FCF9CB000B69C83 /* SpriteKitOpaqueLoggable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpriteKitOpaqueLoggable.swift; sourceTree = "<group>"; };
239243
5E4AF1AC1FDDBDE400B7C9D9 /* Unimplemented.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Unimplemented.swift; sourceTree = "<group>"; };
240244
5E4AF1AE1FDDC12A00B7C9D9 /* LegacyEntrypoints.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyEntrypoints.swift; sourceTree = "<group>"; };
245+
5E5D777F2040BD7D00EBC3A9 /* LogPolicyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogPolicyTests.swift; sourceTree = "<group>"; };
246+
5E5F600A20409D4E007EF0A8 /* LogPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogPolicy.swift; sourceTree = "<group>"; };
241247
5E5FE50A202D13C800E28C3C /* PGLConcurrentMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PGLConcurrentMap.swift; sourceTree = "<group>"; };
242248
5ECE8F901FFCD2A70034D9BC /* LegacyPlaygroundLoggerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyPlaygroundLoggerTests.swift; sourceTree = "<group>"; };
243249
5EE3867320352F3200D625F0 /* CGFloat+CustomOpaqueLoggable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGFloat+CustomOpaqueLoggable.swift"; sourceTree = "<group>"; };
@@ -345,6 +351,7 @@
345351
5E2755D61FB682DE00B69C83 /* LogEntry+Encoding.swift */,
346352
5E2755D01FB6619800B69C83 /* LogPacket.swift */,
347353
5E2755D41FB6807F00B69C83 /* LogPacket+Encoding.swift */,
354+
5E5F600A20409D4E007EF0A8 /* LogPolicy.swift */,
348355
5E2755D21FB672DC00B69C83 /* SendData.swift */,
349356
5E184714202BB72700F01AD1 /* TypeName.swift */,
350357
5E27561B1FC4854C00B69C83 /* Opaque Representations */,
@@ -359,6 +366,7 @@
359366
5E2646341FB64876002DC6B6 /* PlaygroundLoggerTests */ = {
360367
isa = PBXGroup;
361368
children = (
369+
5E5D777F2040BD7D00EBC3A9 /* LogPolicyTests.swift */,
362370
5ECE8F901FFCD2A70034D9BC /* LegacyPlaygroundLoggerTests.swift */,
363371
);
364372
path = PlaygroundLoggerTests;
@@ -892,6 +900,7 @@
892900
5E2756821FCF439D00B69C83 /* NSImage+CustomOpaqueLoggable.swift in Sources */,
893901
5E2755D91FB6836800B69C83 /* LogEncoder.swift in Sources */,
894902
5E184715202BB72700F01AD1 /* TypeName.swift in Sources */,
903+
5E5F600B20409D4E007EF0A8 /* LogPolicy.swift in Sources */,
895904
5E2756791FCF3D3200B69C83 /* CIImage+CustomOpaqueLoggable.swift in Sources */,
896905
5E2756871FCF451900B69C83 /* UIBezierPath+CustomOpaqueLoggable.swift in Sources */,
897906
5E2756841FCF43DD00B69C83 /* NSView+CustomOpaqueLoggable.swift in Sources */,
@@ -917,6 +926,7 @@
917926
isa = PBXSourcesBuildPhase;
918927
buildActionMask = 2147483647;
919928
files = (
929+
5E5D77802040BD7D00EBC3A9 /* LogPolicyTests.swift in Sources */,
920930
5ECE8F911FFCD2A70034D9BC /* LegacyPlaygroundLoggerTests.swift in Sources */,
921931
);
922932
runOnlyForDeploymentPostprocessing = 0;
@@ -925,6 +935,7 @@
925935
isa = PBXSourcesBuildPhase;
926936
buildActionMask = 2147483647;
927937
files = (
938+
5E5D77812040BD7D00EBC3A9 /* LogPolicyTests.swift in Sources */,
928939
5EFE9193203F6CF900E21BAA /* LegacyPlaygroundLoggerTests.swift in Sources */,
929940
);
930941
runOnlyForDeploymentPostprocessing = 0;
@@ -951,6 +962,7 @@
951962
isa = PBXSourcesBuildPhase;
952963
buildActionMask = 2147483647;
953964
files = (
965+
5E5D77822040BD7D00EBC3A9 /* LogPolicyTests.swift in Sources */,
954966
5EFE91D7203F6F8100E21BAA /* LegacyPlaygroundLoggerTests.swift in Sources */,
955967
);
956968
runOnlyForDeploymentPostprocessing = 0;

PlaygroundLogger/PlaygroundLogger/LegacySupport/LegacyEntrypoints.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ fileprivate func legacySendDataStub(_: NSData) -> Void {
3636

3737
@_silgen_name("playground_log_hidden")
3838
public func legacyLog<T>(instance: T, name: String, id: Int, startLine: Int, endLine: Int, startColumn: Int, endColumn: Int) -> AnyObject {
39-
let packet = LogPacket(describingResult: instance, named: name, startLine: startLine, endLine: endLine, startColumn: startColumn, endColumn: endColumn)
39+
let packet = LogPacket(describingResult: instance, named: name, withPolicy: .default, startLine: startLine, endLine: endLine, startColumn: startColumn, endColumn: endColumn)
4040

4141
let data = packet.encode()
4242

PlaygroundLogger/PlaygroundLogger/LogEntry+Reflection.swift

Lines changed: 95 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ fileprivate class DebugQuickLookObjectHook: NSObject {
2020
fileprivate let emptyNameString = ""
2121

2222
extension LogEntry {
23-
init(describing instance: Any, name: String? = nil) {
24-
self = .init(describing: instance, name: name ?? emptyNameString, typeName: nil, summary: nil)
23+
init(describing instance: Any, name: String? = nil, policy: LogPolicy) {
24+
self = .init(describing: instance, name: name ?? emptyNameString, typeName: nil, summary: nil, policy: policy)
2525
}
2626

27-
private init(describing instance: Any, name: String, typeName passedInTypeName: String?, summary passedInSummary: String?) {
27+
private init(describing instance: Any, name: String, typeName passedInTypeName: String?, summary passedInSummary: String?, policy: LogPolicy) {
2828
// TODO: need to handle optionals better (e.g. implicitly unwrap optionality, I think)
2929

3030
// Returns either the passed-in type name/summary or the type name/summary of `instance`.
@@ -47,7 +47,7 @@ extension LogEntry {
4747

4848
// If a type implements the `debugQuickLookObject()` Objective-C method, then get their debug quick look object and use that for logging (by passing it back through this initializer).
4949
else if let debugQuickLookObjectMethod = (instance as AnyObject).debugQuickLookObject, let debugQuickLookObject = debugQuickLookObjectMethod() {
50-
self = .init(describing: debugQuickLookObject, name: name, typeName: typeName, summary: nil)
50+
self = .init(describing: debugQuickLookObject, name: name, typeName: typeName, summary: nil, policy: policy)
5151
}
5252

5353
// Otherwise, first check if this is an interesting CF type before logging structure.
@@ -68,11 +68,11 @@ extension LogEntry {
6868

6969
if mirror.displayStyle == .optional && mirror.children.count == 1 {
7070
// If the mirror displays as an Optional and has exactly one child, then we want to unwrap the optionality and generate a log entry for the child.
71-
self = .init(describing: mirror.children.first!.value, name: name, typeName: typeName, summary: nil)
71+
self = .init(describing: mirror.children.first!.value, name: name, typeName: typeName, summary: nil, policy: policy)
7272
}
7373
else {
7474
// Otherwise, we want to generate a log entry with the structure from the mirror.
75-
self = .init(structureFrom: mirror, name: name, typeName: typeName, summary: summary)
75+
self = .init(structureFrom: mirror, name: name, typeName: typeName, summary: summary, policy: policy)
7676
}
7777
}
7878
}
@@ -83,31 +83,14 @@ extension LogEntry {
8383
self = .opaque(name: name, typeName: typeName, summary: summary, preferBriefSummary: false, representation: playgroundQuickLook.opaqueRepresentation)
8484
}
8585

86-
private static let superclassLogEntryName = "super"
86+
fileprivate static let superclassLogEntryName = "super"
8787

88-
private init(structureFrom mirror: Mirror, name: String, typeName: String, summary: String) {
89-
let totalChildrenCount: Int
90-
var childEntries: [LogEntry] = []
91-
92-
// If our Mirror has a superclassMirror, then we need to include that as the first "child" (and include it in the total children count).
93-
if let superclassMirror = mirror.superclassMirror {
94-
let superclassTypeName = normalizedName(of: superclassMirror.subjectType)
95-
childEntries.append(LogEntry(structureFrom: superclassMirror, name: LogEntry.superclassLogEntryName, typeName: superclassTypeName, summary: superclassTypeName))
96-
97-
totalChildrenCount = Int(mirror.children.count) + 1
98-
}
99-
else {
100-
totalChildrenCount = Int(mirror.children.count)
101-
}
102-
103-
// Next, we need to generate log entries for all of the "real" children of this mirror.
104-
childEntries += mirror.children.map { LogEntry(describing: $0.value, name: $0.label) }
105-
88+
fileprivate init(structureFrom mirror: Mirror, name: String, typeName: String, summary: String, policy: LogPolicy) {
10689
self = .structured(name: name,
10790
typeName: typeName,
10891
summary: summary,
109-
totalChildrenCount: totalChildrenCount,
110-
children: childEntries,
92+
totalChildrenCount: mirror.totalChildCount,
93+
children: mirror.childEntries(using: policy),
11194
disposition: .init(displayStyle: mirror.displayStyle)
11295
)
11396
}
@@ -140,3 +123,88 @@ extension LogEntry.StructuredDisposition {
140123
}
141124
}
142125
}
126+
127+
extension Mirror {
128+
fileprivate var totalChildCount: Int {
129+
if superclassMirror != nil {
130+
return Int(children.count) + 1
131+
}
132+
else {
133+
return Int(children.count)
134+
}
135+
}
136+
137+
fileprivate func childEntries(using policy: LogPolicy) -> [LogEntry] {
138+
let childPolicy: LogPolicy.ChildPolicy = {
139+
switch self.displayStyle ?? .struct {
140+
case .class, .struct, .tuple, .enum:
141+
return policy.aggregateChildPolicy
142+
case .optional, .collection, .dictionary, .set:
143+
return policy.containerChildPolicy
144+
}
145+
}()
146+
147+
func logEntry(forChild child: Mirror.Child) -> LogEntry {
148+
return LogEntry(describing: child.value, name: child.label, policy: policy)
149+
}
150+
151+
func logEntriesForAllChildren() -> [LogEntry] {
152+
let childEntries = children.map(logEntry(forChild:))
153+
if let superclassMirror = superclassMirror {
154+
return [superclassMirror.logEntry(named: LogEntry.superclassLogEntryName, usingPolicy: policy)] + childEntries
155+
}
156+
else {
157+
return childEntries
158+
}
159+
}
160+
161+
func logEntries(forFirstChildren count: Int) -> [LogEntry] {
162+
let numberOfChildren: Int
163+
let superclassEntries: [LogEntry]
164+
if let superclassMirror = superclassMirror {
165+
superclassEntries = [superclassMirror.logEntry(named: LogEntry.superclassLogEntryName, usingPolicy: policy)]
166+
numberOfChildren = count - 1
167+
}
168+
else {
169+
superclassEntries = []
170+
numberOfChildren = count
171+
}
172+
173+
let start = children.startIndex
174+
let max = children.index(start, offsetBy: numberOfChildren)
175+
176+
return superclassEntries + children[start..<max].map(logEntry(forChild:))
177+
}
178+
179+
func logEntries(forLastChildren count: Int) -> [LogEntry] {
180+
let max = children.endIndex
181+
let start = children.index(max, offsetBy: -count)
182+
183+
return children[start..<max].map(logEntry(forChild:))
184+
}
185+
186+
switch childPolicy {
187+
case .all:
188+
return logEntriesForAllChildren()
189+
case let .head(count):
190+
if totalChildCount <= count {
191+
return logEntriesForAllChildren()
192+
}
193+
194+
return logEntries(forFirstChildren: count) + [LogEntry.gap]
195+
case let .headTail(headCount, tailCount):
196+
if totalChildCount <= headCount + tailCount {
197+
return logEntriesForAllChildren()
198+
}
199+
200+
return logEntries(forFirstChildren: headCount) + [LogEntry.gap] + logEntries(forLastChildren: tailCount)
201+
case .none:
202+
return []
203+
}
204+
}
205+
206+
fileprivate func logEntry(named name: String, usingPolicy policy: LogPolicy) -> LogEntry {
207+
let subjectTypeName = normalizedName(of: self.subjectType)
208+
return LogEntry(structureFrom: self, name: name, typeName: subjectTypeName, summary: subjectTypeName, policy: policy)
209+
}
210+
}

PlaygroundLogger/PlaygroundLogger/LogPacket.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ extension LogPacket {
3535
self.logEntry = logEntry
3636
}
3737

38-
init(describingResult result: Any, named name: String, startLine: Int, endLine: Int, startColumn: Int, endColumn: Int, threadID: String? = nil) {
39-
self = .init(logEntry: LogEntry(describing: result, name: name), startLine: startLine, endLine: endLine, startColumn: startColumn, endColumn: endColumn, threadID: threadID)
38+
init(describingResult result: Any, named name: String, withPolicy policy: LogPolicy, startLine: Int, endLine: Int, startColumn: Int, endColumn: Int, threadID: String? = nil) {
39+
self = .init(logEntry: LogEntry(describing: result, name: name, policy: policy), startLine: startLine, endLine: endLine, startColumn: startColumn, endColumn: endColumn, threadID: threadID)
4040
}
4141

4242
init(scopeEntryWithStartLine startLine: Int, endLine: Int, startColumn: Int, endColumn: Int, threadID: String? = nil) {
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//===--- LogPolicy.swift --------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2018 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See http://swift.org/LICENSE.txt for license information
9+
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
struct LogPolicy {
14+
static let `default`: LogPolicy = LogPolicy()
15+
16+
enum ChildPolicy {
17+
/// Indicates that all children should be logged.
18+
case all
19+
/// Indicates that no more than the first `count` children should be logged.
20+
case head(count: Int)
21+
/// Indicates that no more than the first `headCount` and last `tailCount` children should be logged.
22+
case headTail(headCount: Int, tailCount: Int)
23+
/// Indicates that no children should be logged.
24+
case none
25+
}
26+
27+
/// The policy for logging children of aggregates (e.g. classes, structs, enums, tuples).
28+
var aggregateChildPolicy: ChildPolicy
29+
30+
/// The policy for logging children of containers (e.g. optionals, collections, dictionaries, sets).
31+
var containerChildPolicy: ChildPolicy
32+
33+
/// Initializes a new `LogPolicy`.
34+
///
35+
/// - parameter aggregateChildPolicy: The policy to use for logging children of aggregates. Defaults to logging no more than the first 10,000 children.
36+
/// - parameter containerChildPolicy: The policy to use for logging children of collections. Defaults to logging no more than the first 80 children plus the last 20 children.
37+
init(aggregateChildPolicy: ChildPolicy = .head(count: 10_000),
38+
containerChildPolicy: ChildPolicy = .headTail(headCount: 80, tailCount: 20)) {
39+
self.aggregateChildPolicy = aggregateChildPolicy
40+
self.containerChildPolicy = containerChildPolicy
41+
}
42+
}

PlaygroundLogger/PlaygroundLogger/LoggerEntrypoints.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ func logResult(_ result: Any,
1919
endLine: Int,
2020
startColumn: Int,
2121
endColumn: Int) {
22-
let packet = LogPacket(describingResult: result, named: name, startLine: startLine, endLine: endLine, startColumn: startColumn, endColumn: endColumn)
22+
let packet = LogPacket(describingResult: result, named: name, withPolicy: .default, startLine: startLine, endLine: endLine, startColumn: startColumn, endColumn: endColumn)
2323

2424
let data = packet.encode()
2525

0 commit comments

Comments
 (0)