Skip to content

Commit e0fba27

Browse files
authored
Merge pull request #1645 from ahoppen/extended-logging
Log full options with which SourceKit-LSP is initialized
2 parents 3fe8b93 + 78b4955 commit e0fba27

File tree

7 files changed

+246
-173
lines changed

7 files changed

+246
-173
lines changed

Sources/LanguageServerProtocolJSONRPC/LoggableMessageTypes.swift

Lines changed: 33 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -14,37 +14,27 @@ import Foundation
1414
import LanguageServerProtocol
1515
import SKLogging
1616

17-
fileprivate extension Encodable {
18-
var prettyPrintJSON: String {
19-
let encoder = JSONEncoder()
20-
encoder.outputFormatting.insert(.prettyPrinted)
21-
encoder.outputFormatting.insert(.sortedKeys)
22-
guard let data = try? encoder.encode(self) else {
23-
return "\(self)"
24-
}
25-
guard let string = String(data: data, encoding: .utf8) else {
26-
return "\(self)"
27-
}
28-
// Don't escape '/'. Most JSON readers don't need it escaped and it makes
29-
// paths a lot easier to read and copy-paste.
30-
return string.replacingOccurrences(of: "\\/", with: "/")
31-
}
32-
}
33-
3417
// MARK: - RequestType
3518

36-
fileprivate struct AnyRequestType: CustomLogStringConvertible {
19+
package struct AnyRequestType: CustomLogStringConvertible {
3720
let request: any RequestType
3821

39-
public var description: String {
22+
package init(request: any RequestType) {
23+
self.request = request
24+
}
25+
26+
package var description: String {
4027
return """
4128
\(type(of: request).method)
42-
\(request.prettyPrintJSON)
29+
\(request.prettyPrintedJSON)
4330
"""
4431
}
4532

46-
public var redactedDescription: String {
47-
return "\(type(of: request).method)"
33+
package var redactedDescription: String {
34+
return """
35+
\(type(of: request).method)
36+
\(request.prettyPrintedRedactedJSON)
37+
"""
4838
}
4939
}
5040

@@ -56,18 +46,25 @@ extension RequestType {
5646

5747
// MARK: - NotificationType
5848

59-
fileprivate struct AnyNotificationType: CustomLogStringConvertible {
49+
package struct AnyNotificationType: CustomLogStringConvertible {
6050
let notification: any NotificationType
6151

62-
public var description: String {
52+
package init(notification: any NotificationType) {
53+
self.notification = notification
54+
}
55+
56+
package var description: String {
6357
return """
6458
\(type(of: notification).method)
65-
\(notification.prettyPrintJSON)
59+
\(notification.prettyPrintedJSON)
6660
"""
6761
}
6862

69-
public var redactedDescription: String {
70-
return "\(type(of: notification).method)"
63+
package var redactedDescription: String {
64+
return """
65+
\(type(of: notification).method)
66+
\(notification.prettyPrintedRedactedJSON)
67+
"""
7168
}
7269
}
7370

@@ -79,19 +76,24 @@ extension NotificationType {
7976

8077
// MARK: - ResponseType
8178

82-
fileprivate struct AnyResponseType: CustomLogStringConvertible {
79+
package struct AnyResponseType: CustomLogStringConvertible {
8380
let response: any ResponseType
8481

85-
var description: String {
82+
package init(response: any ResponseType) {
83+
self.response = response
84+
}
85+
86+
package var description: String {
8687
return """
8788
\(type(of: response))
88-
\(response.prettyPrintJSON)
89+
\(response.prettyPrintedJSON)
8990
"""
9091
}
9192

92-
var redactedDescription: String {
93+
package var redactedDescription: String {
9394
return """
9495
\(type(of: response))
96+
\(response.prettyPrintedRedactedJSON)
9597
"""
9698
}
9799
}

Sources/SKLogging/CustomLogStringConvertible.swift

Lines changed: 44 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ extension String {
6464
/// A hash value that can be logged in a redacted description without
6565
/// disclosing any private information about the string.
6666
package var hashForLogging: String {
67-
return Insecure.MD5.hash(data: Data(self.utf8)).description
67+
let hash = SHA256.hash(data: Data(self.utf8)).prefix(8).map { String(format: "%02x", $0) }.joined()
68+
return "<private \(hash)>"
6869
}
6970
}
7071

@@ -86,58 +87,51 @@ extension Optional where Wrapped: CustomLogStringConvertible {
8687
}
8788
}
8889

89-
/// A JSON-like description of the object.
90-
public func recursiveDescription(of subject: Any) -> String {
91-
return "{"
92-
+ Mirror(reflecting: subject).children.map { (key, value) in
93-
"\(key ?? "<nil>"): \(String(describing: value))"
94-
}.joined(separator: ", ")
95-
+ "}"
96-
}
97-
98-
fileprivate protocol OptionalProtocol {
99-
associatedtype Wrapped
100-
var asOptional: Wrapped? { get }
101-
}
102-
extension Optional: OptionalProtocol {
103-
var asOptional: Wrapped? { self }
104-
}
105-
106-
/// A JSON-like description of the object that shows trivial values but redacts values that might contain sensitive
107-
/// information.
108-
public func recursiveRedactedDescription(of value: Any) -> String {
109-
switch value {
110-
case let value as Bool:
111-
return value.description
112-
case let value as Int:
113-
return value.description
114-
case let value as Double:
115-
return value.description
116-
case let value as String:
117-
return value.hashForLogging
118-
case let value as any OptionalProtocol:
119-
if let value = value.asOptional {
120-
return recursiveRedactedDescription(of: value)
121-
} else {
122-
return "nil"
90+
extension Encodable {
91+
package var prettyPrintedJSON: String {
92+
let encoder = JSONEncoder()
93+
encoder.outputFormatting.insert(.prettyPrinted)
94+
encoder.outputFormatting.insert(.sortedKeys)
95+
guard let data = try? encoder.encode(self) else {
96+
return "\(self)"
97+
}
98+
guard let string = String(data: data, encoding: .utf8) else {
99+
return "\(self)"
123100
}
124-
default:
125-
break
101+
// Don't escape '/'. Most JSON readers don't need it escaped and it makes
102+
// paths a lot easier to read and copy-paste.
103+
return string.replacingOccurrences(of: "\\/", with: "/")
126104
}
127105

128-
let children = Mirror(reflecting: value).children.sorted { $0.label ?? "" < $1.label ?? "" }
129-
if !children.isEmpty {
130-
let childrenKeyValuePairs = children.map { (label, value) -> String in
131-
let label = label ?? "<nil>"
132-
let value =
133-
if let value = value as? any CustomLogStringConvertible {
134-
value.redactedDescription
135-
} else {
136-
recursiveRedactedDescription(of: value)
137-
}
138-
return "\(label): \(value)"
106+
package var prettyPrintedRedactedJSON: String {
107+
func redact(subject: Any) -> Any {
108+
if let subject = subject as? [String: Any] {
109+
return subject.mapValues { redact(subject: $0) }
110+
} else if let subject = subject as? [Any] {
111+
return subject.map { redact(subject: $0) }
112+
} else if let subject = subject as? String {
113+
return subject.hashForLogging
114+
} else if let subject = subject as? Int {
115+
return subject
116+
} else if let subject = subject as? Double {
117+
return subject
118+
} else if let subject = subject as? Bool {
119+
return subject
120+
} else {
121+
return "<private>"
122+
}
123+
}
124+
125+
guard let encoded = try? JSONEncoder().encode(self),
126+
let jsonObject = try? JSONSerialization.jsonObject(with: encoded),
127+
let data = try? JSONSerialization.data(
128+
withJSONObject: redact(subject: jsonObject),
129+
options: [.prettyPrinted, .sortedKeys]
130+
),
131+
let string = String(data: data, encoding: .utf8)
132+
else {
133+
return "<private>"
139134
}
140-
return "{" + childrenKeyValuePairs.joined(separator: ", ") + "}"
135+
return string
141136
}
142-
return "<private>"
143137
}

Sources/SKLogging/SplitLogMessage.swift

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
///
1818
/// - Note: This will only split along newline boundary. If a single line is longer than `maxChunkSize`, it won't be
1919
/// split. This is fine for compiler argument splitting since a single argument is rarely longer than 800 characters.
20-
package func splitLongMultilineMessage(message: String, maxChunkSize: Int = 800) -> [String] {
20+
package func splitLongMultilineMessage(message: String) -> [String] {
21+
let maxChunkSize = 800
2122
var chunks: [String] = []
2223
for line in message.split(separator: "\n", omittingEmptySubsequences: false) {
2324
if let lastChunk = chunks.last, lastChunk.utf8.count + line.utf8.count < maxChunkSize {
@@ -34,3 +35,34 @@ package func splitLongMultilineMessage(message: String, maxChunkSize: Int = 800)
3435
}
3536
return chunks
3637
}
38+
39+
extension Logger {
40+
/// Implementation detail of `logFullObjectInMultipleLogMessages`
41+
private struct LoggableChunk: CustomLogStringConvertible {
42+
var description: String
43+
var redactedDescription: String
44+
}
45+
46+
package func logFullObjectInMultipleLogMessages(
47+
level: LogLevel = .default,
48+
header: StaticString,
49+
_ subject: some CustomLogStringConvertible
50+
) {
51+
let chunks = splitLongMultilineMessage(message: subject.description)
52+
let redactedChunks = splitLongMultilineMessage(message: subject.redactedDescription)
53+
let maxChunkCount = max(chunks.count, redactedChunks.count)
54+
for i in 0..<maxChunkCount {
55+
let loggableChunk = LoggableChunk(
56+
description: i < chunks.count ? chunks[i] : "",
57+
redactedDescription: i < redactedChunks.count ? redactedChunks[i] : ""
58+
)
59+
self.log(
60+
level: level,
61+
"""
62+
\(header, privacy: .public) (\(i + 1)/\(maxChunkCount))
63+
\(loggableChunk.forLogging)
64+
"""
65+
)
66+
}
67+
}
68+
}

Sources/SKOptions/SourceKitLSPOptions.swift

Lines changed: 7 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import struct TSCBasic.AbsolutePath
2121
///
2222
/// See `ConfigurationFile.md` for a description of the configuration file's behavior.
2323
public struct SourceKitLSPOptions: Sendable, Codable, Equatable, CustomLogStringConvertible {
24-
public struct SwiftPMOptions: Sendable, Codable, Equatable, CustomLogStringConvertible {
24+
public struct SwiftPMOptions: Sendable, Codable, Equatable {
2525
/// Build configuration (debug|release).
2626
///
2727
/// Equivalent to SwiftPM's `--configuration` option.
@@ -94,17 +94,9 @@ public struct SourceKitLSPOptions: Sendable, Codable, Equatable, CustomLogString
9494
disableSandbox: override?.disableSandbox ?? base.disableSandbox
9595
)
9696
}
97-
98-
public var description: String {
99-
recursiveDescription(of: self)
100-
}
101-
102-
public var redactedDescription: String {
103-
recursiveRedactedDescription(of: self)
104-
}
10597
}
10698

107-
public struct CompilationDatabaseOptions: Sendable, Codable, Equatable, CustomLogStringConvertible {
99+
public struct CompilationDatabaseOptions: Sendable, Codable, Equatable {
108100
/// Additional paths to search for a compilation database, relative to a workspace root.
109101
public var searchPaths: [String]?
110102

@@ -118,17 +110,9 @@ public struct SourceKitLSPOptions: Sendable, Codable, Equatable, CustomLogString
118110
) -> CompilationDatabaseOptions {
119111
return CompilationDatabaseOptions(searchPaths: override?.searchPaths ?? base.searchPaths)
120112
}
121-
122-
public var description: String {
123-
recursiveDescription(of: self)
124-
}
125-
126-
public var redactedDescription: String {
127-
recursiveRedactedDescription(of: self)
128-
}
129113
}
130114

131-
public struct FallbackBuildSystemOptions: Sendable, Codable, Equatable, CustomLogStringConvertible {
115+
public struct FallbackBuildSystemOptions: Sendable, Codable, Equatable {
132116
public var cCompilerFlags: [String]?
133117
public var cxxCompilerFlags: [String]?
134118
public var swiftCompilerFlags: [String]?
@@ -157,17 +141,9 @@ public struct SourceKitLSPOptions: Sendable, Codable, Equatable, CustomLogString
157141
sdk: override?.sdk ?? base.sdk
158142
)
159143
}
160-
161-
public var description: String {
162-
recursiveDescription(of: self)
163-
}
164-
165-
public var redactedDescription: String {
166-
recursiveRedactedDescription(of: self)
167-
}
168144
}
169145

170-
public struct IndexOptions: Sendable, Codable, Equatable, CustomLogStringConvertible {
146+
public struct IndexOptions: Sendable, Codable, Equatable {
171147
public var indexStorePath: String?
172148
public var indexDatabasePath: String?
173149
public var indexPrefixMap: [String: String]?
@@ -210,17 +186,9 @@ public struct SourceKitLSPOptions: Sendable, Codable, Equatable, CustomLogString
210186
updateIndexStoreTimeout: override?.updateIndexStoreTimeout ?? base.updateIndexStoreTimeout
211187
)
212188
}
213-
214-
public var description: String {
215-
recursiveDescription(of: self)
216-
}
217-
218-
public var redactedDescription: String {
219-
recursiveRedactedDescription(of: self)
220-
}
221189
}
222190

223-
public struct LoggingOptions: Sendable, Codable, Equatable, CustomLogStringConvertible {
191+
public struct LoggingOptions: Sendable, Codable, Equatable {
224192
/// The level from which one onwards log messages should be written.
225193
public var level: String?
226194
/// Whether potentially sensitive information should be redacted.
@@ -240,14 +208,6 @@ public struct SourceKitLSPOptions: Sendable, Codable, Equatable, CustomLogString
240208
privacyLevel: override?.privacyLevel ?? base.privacyLevel
241209
)
242210
}
243-
244-
public var description: String {
245-
recursiveDescription(of: self)
246-
}
247-
248-
public var redactedDescription: String {
249-
recursiveRedactedDescription(of: self)
250-
}
251211
}
252212

253213
public enum BackgroundPreparationMode: String {
@@ -477,10 +437,10 @@ public struct SourceKitLSPOptions: Sendable, Codable, Equatable, CustomLogString
477437
}
478438

479439
public var description: String {
480-
recursiveDescription(of: self)
440+
self.prettyPrintedJSON
481441
}
482442

483443
public var redactedDescription: String {
484-
recursiveRedactedDescription(of: self)
444+
self.prettyPrintedRedactedJSON
485445
}
486446
}

0 commit comments

Comments
 (0)