Skip to content

Commit 824105f

Browse files
CopilotJoeRobich
andcommitted
Add captureActivityLogs to CSharpExtensionExports
Co-authored-by: JoeRobich <611219+JoeRobich@users.noreply.github.com>
1 parent bb3fa06 commit 824105f

4 files changed

Lines changed: 114 additions & 1 deletion

File tree

src/activateRoslyn.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import TelemetryReporter from '@vscode/extension-telemetry';
1212
import { RoslynLanguageServer } from './lsptoolshost/server/roslynLanguageServer';
1313
import { CSharpDevKitExports } from './csharpDevKitExports';
1414
import { RoslynLanguageServerEvents, ServerState } from './lsptoolshost/server/languageServerEvents';
15-
import { activateRoslynLanguageServer } from './lsptoolshost/activate';
15+
import { activateRoslynLanguageServer, createCaptureActivityLogs } from './lsptoolshost/activate';
1616
import Descriptors from './lsptoolshost/solutionSnapshot/descriptors';
1717
import { getBrokeredServiceContainer } from './lsptoolshost/serviceBroker/brokeredServicesHosting';
1818
import { debugSessionTracker } from './coreclrDebug/provisionalDebugSessionTracker';
@@ -86,6 +86,10 @@ export function activateRoslyn(
8686
getComponentFolder: (componentName) => {
8787
return getComponentFolder(componentName, languageServerOptions);
8888
},
89+
captureActivityLogs: async () => {
90+
const languageServer = await roslynLanguageServerStartedPromise;
91+
return createCaptureActivityLogs(languageServer, razorLogger);
92+
},
8993
};
9094

9195
return exports;

src/csharpExtensionExports.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ export interface OmnisharpExtensionExports {
2424
logDirectory: string;
2525
}
2626

27+
export interface ActivityLogCapture extends vscode.Disposable {
28+
getActivityLogs(): { csharpLog: string; lspTraceLog: string; razorLog: string };
29+
}
30+
2731
export interface CSharpExtensionExports {
2832
isLimitedActivation: false;
2933
initializationFinished: () => Promise<void>;
@@ -32,6 +36,7 @@ export interface CSharpExtensionExports {
3236
determineBrowserType: () => Promise<string | undefined>;
3337
experimental: CSharpExtensionExperimentalExports;
3438
getComponentFolder: (componentName: string) => string;
39+
captureActivityLogs: () => Promise<ActivityLogCapture>;
3540
}
3641

3742
export interface CSharpExtensionExperimentalExports {

src/lsptoolshost/activate.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ import { RazorLogger } from '../razor/src/razorLogger';
3131
import { registerRazorEndpoints } from './razor/razorEndpoints';
3232
import { ObservableLogOutputChannel } from './logging/observableLogOutputChannel';
3333
import { registerSourceGeneratorRefresh } from './generators/sourceGeneratorsRefresh';
34+
import { ActivityLogCapture } from '../csharpExtensionExports';
35+
import { RazorLogObserver } from './logging/loggingUtils';
3436

3537
let _channel: ObservableLogOutputChannel;
3638
let _traceChannel: ObservableLogOutputChannel;
@@ -174,3 +176,36 @@ function getInstalledServerPath(platformInfo: PlatformInformation): string {
174176

175177
return pathWithExtension;
176178
}
179+
180+
/**
181+
* Creates an activity log capture that collects logs from the C#, LSP trace, and Razor channels.
182+
* Sets log levels to Trace for capture. Call dispose() to stop capturing and restore log levels.
183+
*/
184+
export async function createCaptureActivityLogs(
185+
languageServer: RoslynLanguageServer,
186+
razorLogger: RazorLogger
187+
): Promise<ActivityLogCapture> {
188+
const csharpLogObserver = _channel.observe();
189+
const traceLogObserver = _traceChannel.observe();
190+
const razorLogObserver = new RazorLogObserver(razorLogger);
191+
192+
const restoreLogLevels = await languageServer.setLogLevelsForCapture();
193+
razorLogger.traceEnabled = true;
194+
razorLogger.debugEnabled = true;
195+
razorLogger.infoEnabled = true;
196+
197+
return {
198+
getActivityLogs: () => ({
199+
csharpLog: csharpLogObserver.getLog(),
200+
lspTraceLog: traceLogObserver.getLog(),
201+
razorLog: razorLogObserver.getLog(),
202+
}),
203+
dispose: async () => {
204+
csharpLogObserver.dispose();
205+
traceLogObserver.dispose();
206+
razorLogObserver.dispose();
207+
await restoreLogLevels();
208+
await razorLogger.updateLogLevelAsync();
209+
},
210+
};
211+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { describe, test, expect } from '@jest/globals';
7+
import { LogObserver } from '../../../src/lsptoolshost/logging/observableLogOutputChannel';
8+
import { RazorLogObserver } from '../../../src/lsptoolshost/logging/loggingUtils';
9+
10+
describe('captureActivityLogs', () => {
11+
describe('LogObserver', () => {
12+
test('formatLogMessages returns empty string for empty array', () => {
13+
const result = LogObserver.formatLogMessages([]);
14+
expect(result).toBe('');
15+
});
16+
17+
test('formatLogMessages includes timestamp, level, and message', () => {
18+
const timestamp = new Date('2024-01-01T12:00:00.000Z');
19+
const messages = [{ level: 'info' as const, message: 'test message', timestamp }];
20+
const result = LogObserver.formatLogMessages(messages);
21+
expect(result).toContain('2024-01-01T12:00:00.000Z');
22+
expect(result).toContain('INFO');
23+
expect(result).toContain('test message');
24+
});
25+
26+
test('formatLogMessages formats multiple messages with newlines', () => {
27+
const timestamp = new Date('2024-01-01T12:00:00.000Z');
28+
const messages = [
29+
{ level: 'info' as const, message: 'first', timestamp },
30+
{ level: 'error' as const, message: 'second', timestamp },
31+
];
32+
const result = LogObserver.formatLogMessages(messages);
33+
const lines = result.split('\n');
34+
expect(lines).toHaveLength(2);
35+
expect(lines[0]).toContain('INFO');
36+
expect(lines[0]).toContain('first');
37+
expect(lines[1]).toContain('ERROR');
38+
expect(lines[1]).toContain('second');
39+
});
40+
});
41+
42+
describe('RazorLogObserver', () => {
43+
test('formatLogMessages returns empty string for empty array', () => {
44+
const result = RazorLogObserver.formatLogMessages([]);
45+
expect(result).toBe('');
46+
});
47+
48+
test('formatLogMessages includes timestamp and message', () => {
49+
const timestamp = new Date('2024-01-01T12:00:00.000Z');
50+
const messages = [{ message: 'razor log entry', timestamp }];
51+
const result = RazorLogObserver.formatLogMessages(messages);
52+
expect(result).toContain('2024-01-01T12:00:00.000Z');
53+
expect(result).toContain('razor log entry');
54+
});
55+
56+
test('formatLogMessages formats multiple messages with newlines', () => {
57+
const timestamp = new Date('2024-01-01T12:00:00.000Z');
58+
const messages = [
59+
{ message: 'first razor', timestamp },
60+
{ message: 'second razor', timestamp },
61+
];
62+
const result = RazorLogObserver.formatLogMessages(messages);
63+
const lines = result.split('\n');
64+
expect(lines).toHaveLength(2);
65+
expect(lines[0]).toContain('first razor');
66+
expect(lines[1]).toContain('second razor');
67+
});
68+
});
69+
});

0 commit comments

Comments
 (0)