Skip to content

Commit 0c4f659

Browse files
authored
Merge pull request #933 from ahoppen/ahoppen/log-sourcekitd-crash-info
When sourcekitd crashes, log the file contents with which it crashed and the request
2 parents 389cb91 + 1a23153 commit 0c4f659

13 files changed

+76
-49
lines changed

Sources/LSPLogging/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ add_library(LSPLogging STATIC
55
LoggingScope.swift
66
NonDarwinLogging.swift
77
OrLog.swift
8+
SplitLogMessage.swift
89
)
910
set_target_properties(LSPLogging PROPERTIES
1011
INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY})
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
/// Splits `message` on newline characters such that each chunk is at most `maxChunkSize` bytes long.
14+
///
15+
/// The intended use case for this is to split compiler arguments into multiple chunks so that each chunk doesn't exceed
16+
/// the maximum message length of `os_log` and thus won't get truncated.
17+
///
18+
/// - Note: This will only split along newline boundary. If a single line is longer than `maxChunkSize`, it won't be
19+
/// split. This is fine for compiler argument splitting since a single argument is rarely longer than 800 characters.
20+
public func splitLongMultilineMessage(message: String, maxChunkSize: Int = 800) -> [String] {
21+
var chunks: [String] = []
22+
for line in message.split(separator: "\n", omittingEmptySubsequences: false) {
23+
if let lastChunk = chunks.last, lastChunk.utf8.count + line.utf8.count < maxChunkSize {
24+
chunks[chunks.count - 1] += "\n" + line
25+
} else {
26+
chunks.append(String(line))
27+
}
28+
}
29+
return chunks
30+
}

Sources/SKCore/BuildSystemManager.swift

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ fileprivate actor BuildSettingsLogger {
325325
\(settings.workingDirectory ?? "<nil>")
326326
"""
327327

328-
let chunks = splitLongMultilineMessage(message: log, maxChunkSize: 800)
328+
let chunks = splitLongMultilineMessage(message: log)
329329
for (index, chunk) in chunks.enumerated() {
330330
logger.log(
331331
"""
@@ -335,23 +335,4 @@ fileprivate actor BuildSettingsLogger {
335335
)
336336
}
337337
}
338-
339-
/// Splits `message` on newline characters such that each chunk is at most `maxChunkSize` bytes long.
340-
///
341-
/// The intended use case for this is to split compiler arguments into multiple chunks so that each chunk doesn't exceed
342-
/// the maximum message length of `os_log` and thus won't get truncated.
343-
///
344-
/// - Note: This will only split along newline boundary. If a single line is longer than `maxChunkSize`, it won't be
345-
/// split. This is fine for compiler argument splitting since a single argument is rarely longer than 800 characters.
346-
private func splitLongMultilineMessage(message: String, maxChunkSize: Int) -> [String] {
347-
var chunks: [String] = []
348-
for line in message.split(separator: "\n", omittingEmptySubsequences: false) {
349-
if let lastChunk = chunks.last, lastChunk.utf8.count + line.utf8.count < maxChunkSize {
350-
chunks[chunks.count - 1] += "\n" + line
351-
} else {
352-
chunks.append(String(line))
353-
}
354-
}
355-
return chunks
356-
}
357338
}

Sources/SourceKitD/SourceKitD.swift

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,11 @@ extension SourceKitD {
6565

6666
// MARK: - Convenience API for requests.
6767

68-
public func send(_ req: SKDRequestDictionary) async throws -> SKDResponseDictionary {
68+
/// - Parameters:
69+
/// - req: The request to send to sourcekitd.
70+
/// - fileContents: The contents of the file that the request operates on. If sourcekitd crashes, the file contents
71+
/// will be logged.
72+
public func send(_ req: SKDRequestDictionary, fileContents: String?) async throws -> SKDResponseDictionary {
6973
logRequest(req)
7074

7175
let signposter = logger.makeSignposter()
@@ -86,6 +90,24 @@ extension SourceKitD {
8690

8791
guard let dict = sourcekitdResponse.value else {
8892
signposter.endInterval("sourcekitd-request", signposterState, "Error")
93+
if sourcekitdResponse.error == .connectionInterrupted {
94+
let log = """
95+
Request:
96+
\(req.description)
97+
98+
File contents:
99+
\(fileContents ?? "<nil>")
100+
"""
101+
let chunks = splitLongMultilineMessage(message: log)
102+
for (index, chunk) in chunks.enumerated() {
103+
logger.fault(
104+
"""
105+
sourcekitd crashed (\(index + 1)/\(chunks.count))
106+
\(chunk)
107+
"""
108+
)
109+
}
110+
}
89111
throw sourcekitdResponse.error!
90112
}
91113

@@ -95,9 +117,7 @@ extension SourceKitD {
95117
}
96118

97119
private func logRequest(_ request: SKDRequestDictionary) {
98-
// FIXME: Ideally we could log the request key here at the info level but the dictionary is
99-
// readonly.
100-
logger.log(
120+
logger.info(
101121
"""
102122
Sending sourcekitd request:
103123
\(request.forLogging)

Sources/SourceKitLSP/SourceKitServer.swift

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -666,12 +666,7 @@ extension SourceKitServer: MessageHandler {
666666
}
667667

668668
private func handleImpl(_ notification: some NotificationType, from clientID: ObjectIdentifier) async {
669-
logger.log(
670-
"""
671-
Received notification
672-
\(notification.forLogging)
673-
"""
674-
)
669+
logger.log("Received notification: \(notification.forLogging)")
675670

676671
switch notification {
677672
case let notification as InitializedNotification:

Sources/SourceKitLSP/Swift/CodeCompletionSession.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ class CodeCompletionSession {
197197
req[keys.compilerargs] = compileCommand.compilerArgs
198198
}
199199

200-
let dict = try await sourcekitd.send(req)
200+
let dict = try await sourcekitd.send(req, fileContents: snapshot.text)
201201
self.state = .open
202202

203203
guard let completions: SKDResponseArray = dict[keys.results] else {
@@ -230,7 +230,7 @@ class CodeCompletionSession {
230230
req[keys.name] = uri.pseudoPath
231231
req[keys.codecomplete_options] = optionsDictionary(filterText: filterText, options: options)
232232

233-
let dict = try await sourcekitd.send(req)
233+
let dict = try await sourcekitd.send(req, fileContents: snapshot.text)
234234
guard let completions: SKDResponseArray = dict[keys.results] else {
235235
return CompletionList(isIncomplete: false, items: [])
236236
}
@@ -275,7 +275,7 @@ class CodeCompletionSession {
275275
req[keys.offset] = self.utf8StartOffset
276276
req[keys.name] = self.snapshot.uri.pseudoPath
277277
logger.info("Closing code completion session: \(self, privacy: .private)")
278-
_ = try? await sourcekitd.send(req)
278+
_ = try? await sourcekitd.send(req, fileContents: nil)
279279
self.state = .closed
280280
}
281281
}

Sources/SourceKitLSP/Swift/CursorInfo.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ extension SwiftLanguageServer {
107107

108108
appendAdditionalParameters?(skreq)
109109

110-
let dict = try await self.sourcekitd.send(skreq)
110+
let dict = try await self.sourcekitd.send(skreq, fileContents: snapshot.text)
111111

112112
guard let kind: sourcekitd_uid_t = dict[keys.kind] else {
113113
// Nothing to report.

Sources/SourceKitLSP/Swift/OpenInterface.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ extension SwiftLanguageServer {
8282
skreq[keys.compilerargs] = compileCommand.compilerArgs
8383
}
8484

85-
let dict = try await self.sourcekitd.send(skreq)
85+
let dict = try await self.sourcekitd.send(skreq, fileContents: nil)
8686
return InterfaceInfo(contents: dict[keys.sourcetext] ?? "")
8787
}
8888

@@ -103,7 +103,7 @@ extension SwiftLanguageServer {
103103
skreq[keys.sourcefile] = uri.pseudoPath
104104
skreq[keys.usr] = symbol
105105

106-
let dict = try await self.sourcekitd.send(skreq)
106+
let dict = try await self.sourcekitd.send(skreq, fileContents: snapshot.text)
107107
if let offset: Int = dict[keys.offset],
108108
let position = snapshot.positionOf(utf8Offset: offset)
109109
{

Sources/SourceKitLSP/Swift/SemanticRefactoring.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ extension SwiftLanguageServer {
155155
skreq[keys.compilerargs] = compileCommand.compilerArgs
156156
}
157157

158-
let dict = try await self.sourcekitd.send(skreq)
158+
let dict = try await self.sourcekitd.send(skreq, fileContents: snapshot.text)
159159
guard let refactor = SemanticRefactoring(refactorCommand.title, dict, snapshot, self.keys) else {
160160
throw SemanticRefactoringError.noEditsNeeded(uri)
161161
}

Sources/SourceKitLSP/Swift/SemanticTokens.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ extension SwiftLanguageServer {
3131
// FIXME: SourceKit should probably cache this for us.
3232
skreq[keys.compilerargs] = buildSettings.compilerArgs
3333

34-
let dict = try await sourcekitd.send(skreq)
34+
let dict = try await sourcekitd.send(skreq, fileContents: snapshot.text)
3535

3636
guard let skTokens: SKDResponseArray = dict[keys.semantic_tokens] else {
3737
return nil

0 commit comments

Comments
 (0)