1
>
/*---------------------------------------------------------------------------------------------
localOtlpReceiver.ts
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 type { AddressInfo } from 'net';
7
>
import type * as http from 'http';
8
>
import { IDisposable, toDisposable } from '../../../../base/common/lifecycle.js';
9
>
import { ILogService } from '../../../log/common/log.js';
10
>
import { decodeExportTraceRequest, IDecodeResult } from './otlpJsonDecode.js';
11
>
import { IOtlpExportTraceServiceRequest, IOtlpExportTraceServiceResponse } from './otlpJsonTypes.js';
12
>
13
>
/** Path the OTLP/HTTP spec mandates for trace export. */
14
>
export const OTLP_TRACES_PATH = '/v1/traces';
15
>
16
>
/** Default request body cap, matching the collector's `confighttp` default. */
17
>
const DEFAULT_MAX_BODY_BYTES = 64 * 1024 * 1024;
18
>
19
>
/** Callbacks the receiver invokes for each accepted request. */
20
>
export interface IOtlpReceiverHandlers {
21
>
/** Invoked with the decoded spans for every successfully-parsed request. */
22
>
onSpans(result: IDecodeResult): void;
23
>
/**
24
>
* Invoked with the raw request body and content-type so the caller can
25
>
* forward the bytes to an upstream collector unchanged. Called before
26
>
* the receiver responds, but failures here MUST NOT affect the response.
27
>
*/
28
>
onForward?(body: Buffer, contentType: string): void;
29
>
}
30
>
31
>
export interface IOtlpReceiverOptions {
32
>
/**
33
>
* Cap on request body size. Anything larger gets HTTP 413. Defaults to
34
>
* 64 MiB, matching the OpenTelemetry Collector default.
35
>
*/
36
>
readonly maxBodyBytes?: number;
37
>
}
38
>
39
>
export interface ILocalOtlpHttpReceiver extends IDisposable {
40
>
/** Loopback URL clients should POST to (without the trailing `/v1/traces`). */
41
>
readonly baseUrl: string;
42
>
/** Ephemeral port chosen by the OS. */
43
>
readonly port: number;
44
>
}
45
>
46
>
/**
47
>
* Loopback OTLP/HTTP receiver. Listens on `127.0.0.1` at an OS-assigned
48
>
* ephemeral port. Accepts `POST /v1/traces` with `Content-Type: application/json`
49
>
* and forwards the parsed result to the caller's handlers.
50
>
*
51
>
* Only `application/json` is supported. `application/x-protobuf` is rejected
52
>
* with HTTP 415; we explicitly require the SDK to use OTLP/HTTP+JSON for the
53
>
* loopback path.
54
>
*
55
>
* Returns `200 OK` with an empty body on full success, or with a
56
>
* `{ partialSuccess: { rejectedSpans, errorMessage } }` body when the
57
>
* decoder dropped some spans. Per OTLP spec, partial success is NOT a retry
58
>
* signal — the SDK will move on.
59
>
*/
60
>
export async function startLocalOtlpHttpReceiver(
61
>
handlers: IOtlpReceiverHandlers,
62
>
logService: ILogService,
63
>
options: IOtlpReceiverOptions = {},
64
>
): Promise<ILocalOtlpHttpReceiver> {
65
>
const maxBodyBytes = options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;
66
>
const httpModule = await import('http');
67
>
const server = httpModule.createServer();
68
>
69
>
server.on('request', (req, res) => {
70
handleRequest(req, res, handlers, logService, maxBodyBytes).catch(err => {
71
logService.error(`[agentHost-otel] receiver: unhandled error: ${err instanceof Error ? err.message : String(err)}`);