Skip to content

Commit a99c2ae

Browse files
authored
Merge pull request #162 from glennsarti/add-crash-logs
(GH-97) Create a crash file when the language server abends
2 parents 2b12075 + f061e2a commit a99c2ae

File tree

3 files changed

+257
-0
lines changed

3 files changed

+257
-0
lines changed

server/lib/puppet-languageserver/message_router.rb

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,57 @@ def self.document(uri)
1919
return nil if @documents[uri].nil?
2020
@documents[uri].clone
2121
end
22+
23+
def self.document_uris
24+
@documents.keys.dup
25+
end
26+
end
27+
28+
module CrashDump
29+
def self.default_crash_file
30+
File.join(Dir.tmpdir(),'puppet_language_server_crash.txt')
31+
end
32+
33+
def self.write_crash_file(err, filename = nil, additional = {})
34+
# Create the crash text
35+
36+
puppet_version = Puppet.version rescue 'Unknown'
37+
facter_version = Facter.version rescue 'Unknown'
38+
languageserver_version = PuppetLanguageServer.version rescue 'Unknown'
39+
40+
crashtext = <<-TEXT
41+
Puppet Language Server Crash File
42+
-=--=--=--=--=--=--=--=--=--=--=-
43+
#{DateTime.now.strftime("%a %b %e %Y %H:%M:%S %Z")}
44+
Puppet Version #{puppet_version}
45+
Facter Version #{facter_version}
46+
Ruby Version #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}
47+
Language Server Version #{languageserver_version}
48+
49+
Error: #{err.to_s}
50+
51+
Backtrace
52+
---------
53+
#{err.backtrace.join("\n") }
54+
55+
TEXT
56+
# Append the documents in the cache
57+
PuppetLanguageServer::DocumentStore.document_uris.each do |uri|
58+
crashtext += "Document - #{uri}\n---\n#{PuppetLanguageServer::DocumentStore.document(uri)}\n\n"
59+
end
60+
# Append additional objects from the crash
61+
additional.each do |k,v|
62+
crashtext += "#{k}\n---\n#{v.to_s}\n\n"
63+
end
64+
65+
crash_file = filename.nil? ? default_crash_file : filename
66+
File.open(crash_file, 'wb') { |file| file.write(crashtext) }
67+
rescue
68+
# Swallow all errors. Errors in the error handler should not
69+
# terminate the application
70+
end
71+
72+
nil
2273
end
2374

2475
class MessageRouter < JSONRPCHandler
@@ -134,6 +185,12 @@ def receive_request(request)
134185
else
135186
PuppetLanguageServer.log_message(:error, "Unknown RPC method #{request.rpc_method}")
136187
end
188+
rescue => err
189+
PuppetLanguageServer::CrashDump.write_crash_file(err, nil, {
190+
'request' => request.rpc_method,
191+
'params' => request.params,
192+
})
193+
raise
137194
end
138195

139196
def receive_notification(method, params)
@@ -170,6 +227,12 @@ def receive_notification(method, params)
170227
else
171228
PuppetLanguageServer.log_message(:error, "Unknown RPC notification #{method}")
172229
end
230+
rescue => err
231+
PuppetLanguageServer::CrashDump.write_crash_file(err, nil, {
232+
'notification' => method,
233+
'params' => params,
234+
})
235+
raise
173236
end
174237
end
175238
end
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
require 'spec_helper'
2+
3+
describe 'message_router' do
4+
let(:subject_options) { nil }
5+
let(:subject) { PuppetLanguageServer::MessageRouter.new(subject_options) }
6+
7+
describe '#receive_request' do
8+
let(:documents) {{
9+
'file1.rb' => "file1_line1\nfile1_line2\nfile1_line3\nfile1_line4\n"
10+
}}
11+
let(:request_connection) { MockJSONRPCHandler.new() }
12+
let(:request_rpc_method) { nil }
13+
let(:request_params) { { 'crashparam1' => 'crashvalue1'} }
14+
let(:request_id) { 0 }
15+
let(:request) { PuppetLanguageServer::JSONRPCHandler::Request.new(
16+
request_connection,request_id,request_rpc_method,request_params) }
17+
18+
before(:each) do
19+
allow(PuppetLanguageServer).to receive(:log_message)
20+
21+
# Populate the document cache
22+
PuppetLanguageServer::DocumentStore.clear
23+
documents.each { |uri, content| PuppetLanguageServer::DocumentStore.set_document(uri, content) }
24+
end
25+
26+
context 'given a request that raises an error' do
27+
let(:request_rpc_method) { 'puppet/getVersion' }
28+
before(:each) do
29+
@default_crash_file = PuppetLanguageServer::CrashDump.default_crash_file
30+
File.delete(@default_crash_file) if File.exists?(@default_crash_file)
31+
32+
expect(Puppet).to receive(:version).at_least(:once).and_raise('MockError')
33+
end
34+
35+
it 'should create a default crash dump file' do
36+
begin
37+
subject.receive_request(request)
38+
rescue
39+
end
40+
41+
expect(File.exists?(@default_crash_file)).to be(true)
42+
end
43+
44+
context 'the content of the crash dump file' do
45+
before(:each) do
46+
# Force a crash dump to occur
47+
begin
48+
subject.receive_request(request)
49+
rescue
50+
end
51+
@crash_content = File.open(@default_crash_file, 'rb') { |file| file.read }
52+
end
53+
54+
it 'should contain the error text' do
55+
expect(@crash_content).to match(/Error: MockError/)
56+
end
57+
58+
it 'should contain a backtrace' do
59+
expect(@crash_content).to match(/Backtrace/)
60+
expect(@crash_content).to match(/message_router.rb/)
61+
end
62+
63+
it 'should contain the request method' do
64+
expect(@crash_content).to match(/request/)
65+
expect(@crash_content).to match(request_rpc_method)
66+
end
67+
68+
it 'should contain the current document cache' do
69+
documents.each do |uri,content|
70+
expect(@crash_content).to match(uri)
71+
expect(@crash_content).to match(content)
72+
end
73+
end
74+
75+
it 'should contain the request parameters' do
76+
expect(@crash_content).to match(/params/)
77+
request_params.each do |k,v|
78+
expect(@crash_content).to match(k)
79+
expect(@crash_content).to match(v)
80+
end
81+
end
82+
end
83+
end
84+
end
85+
86+
describe '#receive_notification' do
87+
let(:documents) {{
88+
'file1.rb' => "file1_line1\nfile1_line2\nfile1_line3\nfile1_line4\n"
89+
}}
90+
let(:notification_method) { nil }
91+
let(:notification_params) { { 'crashparam2' => 'crashvalue2'} }
92+
93+
before(:each) do
94+
allow(PuppetLanguageServer).to receive(:log_message)
95+
96+
# Populate the document cache
97+
PuppetLanguageServer::DocumentStore.clear
98+
documents.each { |uri, content| PuppetLanguageServer::DocumentStore.set_document(uri, content) }
99+
end
100+
101+
context 'given a request that raises an error' do
102+
let(:notification_method) { 'exit' }
103+
before(:each) do
104+
@default_crash_file = PuppetLanguageServer::CrashDump.default_crash_file
105+
File.delete(@default_crash_file) if File.exists?(@default_crash_file)
106+
107+
expect(subject).to receive(:close_connection).and_raise('MockError')
108+
end
109+
110+
it 'should create a default crash dump file' do
111+
begin
112+
subject.receive_notification(notification_method, notification_params)
113+
rescue
114+
end
115+
116+
expect(File.exists?(@default_crash_file)).to be(true)
117+
end
118+
119+
context 'the content of the crash dump file' do
120+
before(:each) do
121+
# Force a crash dump to occur
122+
begin
123+
subject.receive_notification(notification_method, notification_params)
124+
rescue
125+
end
126+
@crash_content = File.open(@default_crash_file, 'rb') { |file| file.read }
127+
end
128+
129+
it 'should contain the error text' do
130+
expect(@crash_content).to match(/Error: MockError/)
131+
end
132+
133+
it 'should contain a backtrace' do
134+
expect(@crash_content).to match(/Backtrace/)
135+
expect(@crash_content).to match(/message_router.rb/)
136+
end
137+
138+
it 'should contain the notification name' do
139+
expect(@crash_content).to match(/notification/)
140+
expect(@crash_content).to match(notification_method)
141+
end
142+
143+
it 'should contain the current document cache' do
144+
documents.each do |uri,content|
145+
expect(@crash_content).to match(uri)
146+
expect(@crash_content).to match(content)
147+
end
148+
end
149+
150+
it 'should contain the request parameters' do
151+
expect(@crash_content).to match(/params/)
152+
notification_params.each do |k,v|
153+
expect(@crash_content).to match(k)
154+
expect(@crash_content).to match(v)
155+
end
156+
end
157+
end
158+
end
159+
end
160+
end

server/spec/unit/puppet-languageserver/message_router_spec.rb

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,23 @@
2222
allow(PuppetLanguageServer).to receive(:log_message)
2323
end
2424

25+
context 'given a request that raises an error' do
26+
let(:request_rpc_method) { 'puppet/getVersion' }
27+
before(:each) do
28+
expect(Puppet).to receive(:version).and_raise('MockError')
29+
allow(PuppetLanguageServer::CrashDump).to receive(:write_crash_file)
30+
end
31+
32+
it 'should raise an error' do
33+
expect{ subject.receive_request(request) }.to raise_error(/MockError/)
34+
end
35+
36+
it 'should call PuppetLanguageServer::CrashDump.write_crash_file' do
37+
expect(PuppetLanguageServer::CrashDump).to receive(:write_crash_file)
38+
expect{ subject.receive_request(request) }.to raise_error(/MockError/)
39+
end
40+
end
41+
2542
# initialize - https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#initialize
2643
context 'given an initialize request' do
2744
let(:request_rpc_method) { 'initialize' }
@@ -386,6 +403,23 @@
386403
let(:notification_method) { nil }
387404
let(:notification_params) { {} }
388405

406+
context 'given a notification that raises an error' do
407+
let(:notification_method) { 'exit' }
408+
before(:each) do
409+
expect(subject).to receive(:close_connection).and_raise('MockError')
410+
allow(PuppetLanguageServer::CrashDump).to receive(:write_crash_file)
411+
end
412+
413+
it 'should raise an error' do
414+
expect{ subject.receive_notification(notification_method, notification_params) }.to raise_error(/MockError/)
415+
end
416+
417+
it 'should call PuppetLanguageServer::CrashDump.write_crash_file' do
418+
expect(PuppetLanguageServer::CrashDump).to receive(:write_crash_file)
419+
expect{ subject.receive_notification(notification_method, notification_params) }.to raise_error(/MockError/)
420+
end
421+
end
422+
389423
# initialized - https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#initialized
390424
context 'given an initialized notification' do
391425
let(:notification_method) { 'initialized' }

0 commit comments

Comments
 (0)