Skip to content

Commit 056d922

Browse files
authored
Add telemetry context to track handler source and trace a request thr… (#225)
1 parent 07c7d05 commit 056d922

File tree

7 files changed

+571
-63
lines changed

7 files changed

+571
-63
lines changed

src/server/CfnServer.ts

Lines changed: 126 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import {
4949
describeChangeSetHandler,
5050
} from '../handlers/StackHandler';
5151
import { LspComponents } from '../protocol/LspComponents';
52+
import { withTelemetryContext } from '../telemetry/TelemetryContext';
5253
import { closeSafely } from '../utils/Closeable';
5354
import { CfnExternal } from './CfnExternal';
5455
import { CfnInfraCore } from './CfnInfraCore';
@@ -88,61 +89,139 @@ export class CfnServer {
8889
}
8990

9091
private setupHandlers() {
91-
this.lsp.documents.onDidOpen(didOpenHandler(this.components));
92-
this.lsp.documents.onDidChangeContent(didChangeHandler(this.lsp.documents, this.components));
93-
this.lsp.documents.onDidClose(didCloseHandler(this.components));
94-
this.lsp.documents.onDidSave(didSaveHandler(this.components));
92+
this.lsp.documents.onDidOpen(withTelemetryContext('Document.Open', didOpenHandler(this.components)));
93+
this.lsp.documents.onDidChangeContent(
94+
withTelemetryContext('Document.Change', didChangeHandler(this.lsp.documents, this.components)),
95+
);
96+
this.lsp.documents.onDidClose(withTelemetryContext('Document.Close', didCloseHandler(this.components)));
97+
this.lsp.documents.onDidSave(withTelemetryContext('Document.Save', didSaveHandler(this.components)));
9598

96-
this.lsp.handlers.onCompletion(completionHandler(this.components));
97-
this.lsp.handlers.onHover(hoverHandler(this.components));
98-
this.lsp.handlers.onExecuteCommand(executionHandler(this.lsp.documents, this.components));
99-
this.lsp.handlers.onCodeAction(codeActionHandler(this.components));
100-
this.lsp.handlers.onDefinition(definitionHandler(this.components));
101-
this.lsp.handlers.onDocumentSymbol(documentSymbolHandler(this.components));
102-
this.lsp.handlers.onDidChangeConfiguration(configurationHandler(this.components));
103-
this.lsp.handlers.onCodeLens(codeLensHandler(this.components));
99+
this.lsp.handlers.onCompletion(withTelemetryContext('Completion', completionHandler(this.components)));
100+
this.lsp.handlers.onHover(withTelemetryContext('Hover', hoverHandler(this.components)));
101+
this.lsp.handlers.onExecuteCommand(
102+
withTelemetryContext('Execution', executionHandler(this.lsp.documents, this.components)),
103+
);
104+
this.lsp.handlers.onCodeAction(withTelemetryContext('Code.Action', codeActionHandler(this.components)));
105+
this.lsp.handlers.onDefinition(withTelemetryContext('Definition', definitionHandler(this.components)));
106+
this.lsp.handlers.onDocumentSymbol(
107+
withTelemetryContext('Document.Symbol', documentSymbolHandler(this.components)),
108+
);
109+
this.lsp.handlers.onDidChangeConfiguration(
110+
withTelemetryContext('Configuration', configurationHandler(this.components)),
111+
);
112+
this.lsp.handlers.onCodeLens(withTelemetryContext('Code.Lens', codeLensHandler(this.components)));
104113

105-
this.lsp.authHandlers.onIamCredentialsUpdate(iamCredentialsUpdateHandler(this.components));
106-
this.lsp.authHandlers.onIamCredentialsDelete(iamCredentialsDeleteHandler(this.components));
114+
this.lsp.authHandlers.onIamCredentialsUpdate(
115+
withTelemetryContext('Auth.Update', iamCredentialsUpdateHandler(this.components)),
116+
);
117+
this.lsp.authHandlers.onIamCredentialsDelete(
118+
withTelemetryContext('Auth.Delete', iamCredentialsDeleteHandler(this.components)),
119+
);
107120

108-
this.lsp.stackHandlers.onGetParameters(getParametersHandler(this.components));
109-
this.lsp.stackHandlers.onGetTemplateArtifacts(getTemplateArtifactsHandler(this.components));
110-
this.lsp.stackHandlers.onCreateValidation(createValidationHandler(this.components));
111-
this.lsp.stackHandlers.onGetCapabilities(getCapabilitiesHandler(this.components));
112-
this.lsp.stackHandlers.onGetTemplateResources(getTemplateResourcesHandler(this.components));
113-
this.lsp.stackHandlers.onCreateDeployment(createDeploymentHandler(this.components));
114-
this.lsp.stackHandlers.onGetValidationStatus(getValidationStatusHandler(this.components));
115-
this.lsp.stackHandlers.onGetDeploymentStatus(getDeploymentStatusHandler(this.components));
116-
this.lsp.stackHandlers.onDescribeValidationStatus(describeValidationStatusHandler(this.components));
117-
this.lsp.stackHandlers.onDescribeDeploymentStatus(describeDeploymentStatusHandler(this.components));
118-
this.lsp.stackHandlers.onDeleteChangeSet(deleteChangeSetHandler(this.components));
119-
this.lsp.stackHandlers.onGetChangeSetDeletionStatus(getChangeSetDeletionStatusHandler(this.components));
121+
this.lsp.stackHandlers.onGetParameters(
122+
withTelemetryContext('Stack.Get.Params', getParametersHandler(this.components)),
123+
);
124+
this.lsp.stackHandlers.onGetTemplateArtifacts(
125+
withTelemetryContext('Stack.Template.Artifacts', getTemplateArtifactsHandler(this.components)),
126+
);
127+
this.lsp.stackHandlers.onCreateValidation(
128+
withTelemetryContext('Stack.Create.Validate', createValidationHandler(this.components)),
129+
);
130+
this.lsp.stackHandlers.onGetCapabilities(
131+
withTelemetryContext('Stack.Capabilities', getCapabilitiesHandler(this.components)),
132+
);
133+
this.lsp.stackHandlers.onGetTemplateResources(
134+
withTelemetryContext('Stack.Template.Resources', getTemplateResourcesHandler(this.components)),
135+
);
136+
this.lsp.stackHandlers.onCreateDeployment(
137+
withTelemetryContext('Stack.Create.Deployment', createDeploymentHandler(this.components)),
138+
);
139+
this.lsp.stackHandlers.onGetValidationStatus(
140+
withTelemetryContext('Stack.Validation.Status', getValidationStatusHandler(this.components)),
141+
);
142+
this.lsp.stackHandlers.onGetDeploymentStatus(
143+
withTelemetryContext('Stack.Deployment.Status', getDeploymentStatusHandler(this.components)),
144+
);
145+
this.lsp.stackHandlers.onDescribeValidationStatus(
146+
withTelemetryContext('Stack.Describe.Validation.Status', describeValidationStatusHandler(this.components)),
147+
);
148+
this.lsp.stackHandlers.onDescribeDeploymentStatus(
149+
withTelemetryContext('Stack.Describe.Deployment.Status', describeDeploymentStatusHandler(this.components)),
150+
);
151+
this.lsp.stackHandlers.onDeleteChangeSet(
152+
withTelemetryContext('Stack.Delete.ChangeSet', deleteChangeSetHandler(this.components)),
153+
);
154+
this.lsp.stackHandlers.onGetChangeSetDeletionStatus(
155+
withTelemetryContext(
156+
'Stack.Get.ChangeSet.Deletion.Status',
157+
getChangeSetDeletionStatusHandler(this.components),
158+
),
159+
);
120160
this.lsp.stackHandlers.onDescribeChangeSetDeletionStatus(
121-
describeChangeSetDeletionStatusHandler(this.components),
122-
);
123-
this.lsp.stackHandlers.onListStacks(listStacksHandler(this.components));
124-
this.lsp.stackHandlers.onListChangeSets(listChangeSetsHandler(this.components));
125-
this.lsp.stackHandlers.onListStackResources(listStackResourcesHandler(this.components));
126-
this.lsp.stackHandlers.onDescribeChangeSet(describeChangeSetHandler(this.components));
127-
this.lsp.stackHandlers.onGetStackTemplate(getManagedResourceStackTemplateHandler(this.components));
128-
this.lsp.stackHandlers.onGetStackEvents(getStackEventsHandler(this.components));
129-
this.lsp.stackHandlers.onClearStackEvents(clearStackEventsHandler(this.components));
130-
this.lsp.stackHandlers.onDescribeStack(describeStackHandler(this.components));
161+
withTelemetryContext(
162+
'Stack.Describe.ChangeSet.Deletion.Status',
163+
describeChangeSetDeletionStatusHandler(this.components),
164+
),
165+
);
166+
this.lsp.stackHandlers.onListStacks(withTelemetryContext('Stack.List', listStacksHandler(this.components)));
167+
this.lsp.stackHandlers.onListChangeSets(
168+
withTelemetryContext('Stack.List.ChangeSets', listChangeSetsHandler(this.components)),
169+
);
170+
this.lsp.stackHandlers.onListStackResources(
171+
withTelemetryContext('Stack.List.Resources', listStackResourcesHandler(this.components)),
172+
);
173+
this.lsp.stackHandlers.onDescribeChangeSet(
174+
withTelemetryContext('Stack.Describe.ChangeSet', describeChangeSetHandler(this.components)),
175+
);
176+
this.lsp.stackHandlers.onGetStackTemplate(
177+
withTelemetryContext('Stack.Get.Template', getManagedResourceStackTemplateHandler(this.components)),
178+
);
179+
this.lsp.stackHandlers.onGetStackEvents(
180+
withTelemetryContext('Stack.Get.Events', getStackEventsHandler(this.components)),
181+
);
182+
this.lsp.stackHandlers.onClearStackEvents(
183+
withTelemetryContext('Stack.Clear.Events', clearStackEventsHandler(this.components)),
184+
);
185+
this.lsp.stackHandlers.onDescribeStack(
186+
withTelemetryContext('Stack.Describe', describeStackHandler(this.components)),
187+
);
131188

132-
this.lsp.cfnEnvironmentHandlers.onParseCfnEnvironmentFiles(parseCfnEnvironmentFilesHandler());
189+
this.lsp.cfnEnvironmentHandlers.onParseCfnEnvironmentFiles(
190+
withTelemetryContext('Cfn.Environment.Parse', parseCfnEnvironmentFilesHandler()),
191+
);
133192

134-
this.lsp.relatedResourcesHandlers.onGetAuthoredResourceTypes(getAuthoredResourceTypesHandler(this.components));
135-
this.lsp.relatedResourcesHandlers.onGetRelatedResourceTypes(getRelatedResourceTypesHandler(this.components));
136-
this.lsp.relatedResourcesHandlers.onInsertRelatedResources(insertRelatedResourcesHandler(this.components));
193+
this.lsp.relatedResourcesHandlers.onGetAuthoredResourceTypes(
194+
withTelemetryContext('Related.Resources.Get.Authored', getAuthoredResourceTypesHandler(this.components)),
195+
);
196+
this.lsp.relatedResourcesHandlers.onGetRelatedResourceTypes(
197+
withTelemetryContext('Related.Resources.Get.Related', getRelatedResourceTypesHandler(this.components)),
198+
);
199+
this.lsp.relatedResourcesHandlers.onInsertRelatedResources(
200+
withTelemetryContext('Related.Resources.Insert', insertRelatedResourcesHandler(this.components)),
201+
);
137202

138-
this.lsp.resourceHandlers.onListResources(listResourcesHandler(this.components));
139-
this.lsp.resourceHandlers.onRefreshResourceList(refreshResourceListHandler(this.components));
140-
this.lsp.resourceHandlers.onSearchResource(searchResourceHandler(this.components));
141-
this.lsp.resourceHandlers.onGetResourceTypes(getResourceTypesHandler(this.components));
142-
this.lsp.resourceHandlers.onResourceStateImport(importResourceStateHandler(this.components));
143-
this.lsp.resourceHandlers.onStackMgmtInfo(getStackMgmtInfo(this.components));
203+
this.lsp.resourceHandlers.onListResources(
204+
withTelemetryContext('Resource.List', listResourcesHandler(this.components)),
205+
);
206+
this.lsp.resourceHandlers.onRefreshResourceList(
207+
withTelemetryContext('Resource.Refresh.List', refreshResourceListHandler(this.components)),
208+
);
209+
this.lsp.resourceHandlers.onSearchResource(
210+
withTelemetryContext('Resource.Search', searchResourceHandler(this.components)),
211+
);
212+
this.lsp.resourceHandlers.onGetResourceTypes(
213+
withTelemetryContext('Resource.Get.Types', getResourceTypesHandler(this.components)),
214+
);
215+
this.lsp.resourceHandlers.onResourceStateImport(
216+
withTelemetryContext('Resource.State.Import', importResourceStateHandler(this.components)),
217+
);
218+
this.lsp.resourceHandlers.onStackMgmtInfo(
219+
withTelemetryContext('Resource.Stack.Mgmt.Info', getStackMgmtInfo(this.components)),
220+
);
144221

145-
this.lsp.s3Handlers.onUploadFile(uploadFileToS3Handler(this.components));
222+
this.lsp.s3Handlers.onUploadFile(
223+
withTelemetryContext('S3.Upload.File', uploadFileToS3Handler(this.components)),
224+
);
146225
}
147226

148227
async close(): Promise<void> {

src/telemetry/OTELInstrumentation.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,10 +100,9 @@ export function otelSdk(clientId: string, client?: ClientInfo) {
100100
'@opentelemetry/instrumentation-tedious': { enabled: false },
101101
'@opentelemetry/instrumentation-undici': { enabled: false },
102102
'@opentelemetry/instrumentation-winston': { enabled: false },
103+
'@opentelemetry/instrumentation-aws-sdk': { enabled: false },
103104

104-
'@opentelemetry/instrumentation-aws-sdk': {
105-
enabled: true,
106-
},
105+
// Only enable system level instrumentation
107106
'@opentelemetry/instrumentation-runtime-node': {
108107
enabled: true,
109108
monitoringPrecision: ExportIntervalSeconds * 1000,

src/telemetry/README.md

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,18 @@ Without telemetry, we cannot objectively evaluate if suggestions are accurate or
2525
### Metrics Metadata
2626
Every metric includes the following metadata attributes:
2727

28-
| Attribute | Description | Example |
29-
|---|---|------------------------------------------------------------|
30-
| `service` | Language server name and version | `aws-cloudformation-languageserver-1.2.3` |
31-
| `service.env` | Node environment and AWS environment | `production-prod` |
32-
| `client.id` | Unique session identifier (UUID) | `1111-2222-3333-4444` |
33-
| `client.type` | LSP client name and version | `vscode-1.85.0` |
34-
| `machine.type` | OS type, platform, architecture, version | `Darwin-darwin-arm64-arm64-22.1.0` |
35-
| `process.type` | Process platform and architecture | `darwin-arm64` |
36-
| `process.version` | Node.js and V8 versions | `node=22.18.0 v8=12.4.254.21-node.27 uv=1.51.0 modules=127` |
37-
| `OTelLib` | Operation name | `Hover`, `AutoComplete`, etc. |
28+
| Attribute | Description | Example |
29+
|---|---------------------------------------------------|-------------------------------------------------------------|
30+
| `service` | Language server name and version | `aws-cloudformation-languageserver-1.2.3` |
31+
| `service.env` | Node environment and AWS environment | `production-prod` |
32+
| `client.id` | Unique session identifier (UUID) | `1111-2222-3333-4444` |
33+
| `client.type` | LSP client name and version | `vscode-1.85.0` |
34+
| `machine.type` | OS type, platform, architecture, version | `Darwin-darwin-arm64-arm64-22.1.0` |
35+
| `process.type` | Process platform and architecture | `darwin-arm64` |
36+
| `process.version` | Node.js and V8 versions | `node=22.18.0 v8=12.4.254.21-node.27 uv=1.51.0 modules=127` |
37+
| `OTelLib` | Operation name | `Hover`, `Completion`, etc. |
38+
| `HandlerSource` | Request handler that initiated an operation | `Document.Open`, `Stack.List`, `Resource.Search`, etc. |
39+
| `RequestId` | Random unique identifier for each request (UUID) | `5555-6666-7777-8888` |
3840

3941
## Data Transmission
4042
Metrics export every 30 seconds via HTTPS with TLS 1.2+ encryption using OpenTelemetry Protocol (OTLP).

src/telemetry/ScopedTelemetry.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
} from '@opentelemetry/api';
1414
import { Closeable } from '../utils/Closeable';
1515
import { typeOf } from '../utils/TypeCheck';
16+
import { TelemetryContext } from './TelemetryContext';
1617

1718
export type MetricConfig = {
1819
description?: string;
@@ -247,8 +248,6 @@ export class ScopedTelemetry implements Closeable {
247248
}
248249
}
249250

250-
const AwsEmfStorageResolution = 1; // High-resolution metrics (1-second granularity) https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/exporter/awsemfexporter#metric-attributes
251-
252251
function generateConfig(config?: MetricConfig) {
253252
const { attributes = {}, unit = '1', valueType = ValueType.DOUBLE, ...options } = config ?? {};
254253
return {
@@ -258,5 +257,9 @@ function generateConfig(config?: MetricConfig) {
258257
}
259258

260259
function generateAttr(attributes?: Attributes): Attributes {
261-
return { 'aws.emf.storage_resolution': AwsEmfStorageResolution, ...attributes };
260+
return {
261+
'aws.emf.storage_resolution': 1, // High-resolution metrics (1-second granularity) https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/exporter/awsemfexporter#metric-attributes
262+
...TelemetryContext.getContext(),
263+
...attributes,
264+
};
262265
}

src/telemetry/TelemetryContext.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { AsyncLocalStorage } from 'async_hooks';
2+
import { v4 } from 'uuid';
3+
4+
type TelemetryContextType = {
5+
HandlerSource?: string;
6+
RequestId?: string;
7+
};
8+
9+
export class TelemetryContext {
10+
private static readonly storage = new AsyncLocalStorage<TelemetryContextType>();
11+
12+
static run<T>(handler: string, fn: () => T): T {
13+
return this.storage.run({ HandlerSource: handler, RequestId: v4() }, fn);
14+
}
15+
16+
static getContext(): TelemetryContextType {
17+
return (
18+
this.storage.getStore() ?? {
19+
HandlerSource: 'Unknown',
20+
}
21+
);
22+
}
23+
}
24+
25+
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-argument */
26+
type Handler = (...args: any[]) => any;
27+
28+
export function withTelemetryContext<T extends Handler>(handlerName: string, handler: T): T {
29+
return ((...args: any[]) => {
30+
return TelemetryContext.run(handlerName, () => {
31+
return handler(...args);
32+
});
33+
}) as T;
34+
}

0 commit comments

Comments
 (0)