Skip to content

Commit

Permalink
feat(mockotlpserver): add 'json' and 'json2' output modes
Browse files Browse the repository at this point in the history
    node lib/mockotlpserver.js --help
    node lib/mockotlpserver.js -o json

The conversion to a normalized JSON that is the same, regardless of the
incoming OTLP protocol was a bit challenging. So far only *trace*
data has been looked at. Supporting metrics and logs OTLP requests
nicely may require more work.
  • Loading branch information
trentm committed Dec 22, 2023
1 parent 33a2365 commit 73432b7
Show file tree
Hide file tree
Showing 5 changed files with 534 additions and 31 deletions.
43 changes: 41 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

212 changes: 212 additions & 0 deletions packages/mockotlpserver/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
A mock OTLP server/receiver for development.

`mockotlpserver` starts HTTP and gRPC servers (on the default ports) for
receiving OTLP requests. The data in those requests are printed to the
console. Various output formats are supported.

# Install

(This package is not yet published to npm, so you'll need a clone of
this repo.)

git clone [email protected]:elastic/elastic-otel-node.git
cd elastic-otel-node
npm ci
cd packages/mockotlpserver

# Usage

The use the mock server, (a) start the server then (b) send OTLP data to it.

node lib/mockotlpserver.js

By default it will output received OTLP data using Node.js's `inspect`
format (used under the hood for `console.log`). This shows the complete
object structure of the received data. For example, using an example script
that uses the OpenTelemetry NodeSDK to trace an HTTP request/response:

cd examples/
node -r ./telemetry.js simple-http-request.js

<details>
<summary>will yield output close to the following:</summary>

```
% node lib/mockotlpserver.js
{"name":"mockotlpserver","level":30,"msg":"OTLP/HTTP listening at http://[::1]:4318","time":"2023-12-22T04:32:39.016Z"}
{"name":"mockotlpserver","level":30,"msg":"OTLP/gRPC listening at http://localhost:4317","time":"2023-12-22T04:32:39.019Z"}
{"name":"mockotlpserver","level":30,"msg":"UI listening at http://localhost:8080","time":"2023-12-22T04:32:39.019Z"}
ExportTraceServiceRequest {
resourceSpans: [
ResourceSpans {
scopeSpans: [
ScopeSpans {
spans: [
Span {
attributes: [
KeyValue { key: 'http.url', value: AnyValue { stringValue: 'http://localhost:3000/' } },
KeyValue { key: 'http.host', value: AnyValue { stringValue: 'localhost:3000' } },
KeyValue { key: 'net.host.name', value: AnyValue { stringValue: 'localhost' } },
KeyValue { key: 'http.method', value: AnyValue { stringValue: 'GET' } },
KeyValue { key: 'http.scheme', value: AnyValue { stringValue: 'http' } },
KeyValue { key: 'http.target', value: AnyValue { stringValue: '/' } },
KeyValue { key: 'http.flavor', value: AnyValue { stringValue: '1.1' } },
KeyValue { key: 'net.transport', value: AnyValue { stringValue: 'ip_tcp' } },
KeyValue { key: 'net.host.ip', value: AnyValue { stringValue: '::1' } },
KeyValue { key: 'net.host.port', value: AnyValue { intValue: Long { low: 3000, high: 0, unsigned: false } } },
KeyValue { key: 'net.peer.ip', value: AnyValue { stringValue: '::1' } },
KeyValue { key: 'net.peer.port', value: AnyValue { intValue: Long { low: 62614, high: 0, unsigned: false } } },
KeyValue { key: 'http.status_code', value: AnyValue { intValue: Long { low: 200, high: 0, unsigned: false } } },
KeyValue { key: 'http.status_text', value: AnyValue { stringValue: 'OK' } }
],
events: [],
links: [],
traceId: Buffer(16) [Uint8Array] [
218, 252, 159, 205, 143, 43,
13, 82, 26, 194, 84, 158,
12, 241, 97, 50
],
spanId: Buffer(8) [Uint8Array] [
235, 244, 225,
251, 215, 244,
158, 97
],
parentSpanId: Buffer(8) [Uint8Array] [
192, 254, 88, 214,
252, 178, 90, 110
],
name: 'GET',
kind: 2,
startTimeUnixNano: Long { low: -1485868864, high: 396561708, unsigned: true },
endTimeUnixNano: Long { low: -1481201225, high: 396561708, unsigned: true },
droppedAttributesCount: 0,
droppedEventsCount: 0,
droppedLinksCount: 0,
status: Status { code: 0 }
},
Span {
attributes: [
KeyValue { key: 'http.url', value: AnyValue { stringValue: 'http://localhost:3000/' } },
KeyValue { key: 'http.method', value: AnyValue { stringValue: 'GET' } },
KeyValue { key: 'http.target', value: AnyValue { stringValue: '/' } },
KeyValue { key: 'net.peer.name', value: AnyValue { stringValue: 'localhost' } },
KeyValue { key: 'http.host', value: AnyValue { stringValue: 'localhost:3000' } },
KeyValue { key: 'net.peer.ip', value: AnyValue { stringValue: '::1' } },
KeyValue { key: 'net.peer.port', value: AnyValue { intValue: Long { low: 3000, high: 0, unsigned: false } } },
KeyValue {
key: 'http.response_content_length_uncompressed',
value: AnyValue { intValue: Long { low: 4, high: 0, unsigned: false } }
},
KeyValue { key: 'http.status_code', value: AnyValue { intValue: Long { low: 200, high: 0, unsigned: false } } },
KeyValue { key: 'http.status_text', value: AnyValue { stringValue: 'OK' } },
KeyValue { key: 'http.flavor', value: AnyValue { stringValue: '1.1' } },
KeyValue { key: 'net.transport', value: AnyValue { stringValue: 'ip_tcp' } }
],
events: [],
links: [],
traceId: Buffer(16) [Uint8Array] [
218, 252, 159, 205, 143, 43,
13, 82, 26, 194, 84, 158,
12, 241, 97, 50
],
spanId: Buffer(8) [Uint8Array] [
192, 254, 88, 214,
252, 178, 90, 110
],
name: 'GET',
kind: 3,
startTimeUnixNano: Long { low: -1494868864, high: 396561708, unsigned: true },
endTimeUnixNano: Long { low: -1478129996, high: 396561708, unsigned: true },
droppedAttributesCount: 0,
droppedEventsCount: 0,
droppedLinksCount: 0,
status: Status { code: 0 }
}
],
scope: InstrumentationScope { attributes: [], name: '@opentelemetry/instrumentation-http', version: '0.45.1' }
}
],
resource: Resource {
attributes: [
KeyValue { key: 'service.name', value: AnyValue { stringValue: 'simple-http-request' } },
KeyValue { key: 'telemetry.sdk.language', value: AnyValue { stringValue: 'nodejs' } },
KeyValue { key: 'telemetry.sdk.name', value: AnyValue { stringValue: 'opentelemetry' } },
KeyValue { key: 'telemetry.sdk.version', value: AnyValue { stringValue: '1.19.0' } },
KeyValue { key: 'process.pid', value: AnyValue { intValue: Long { low: 20595, high: 0, unsigned: false } } },
KeyValue { key: 'process.executable.name', value: AnyValue { stringValue: 'node' } },
KeyValue {
key: 'process.executable.path',
value: AnyValue { stringValue: '/Users/trentm/.nvm/versions/node/v18.18.2/bin/node' }
},
KeyValue {
key: 'process.command_args',
value: AnyValue {
arrayValue: ArrayValue {
values: [
AnyValue { stringValue: '/Users/trentm/.nvm/versions/node/v18.18.2/bin/node' },
AnyValue { stringValue: '-r' },
AnyValue { stringValue: './telemetry.js' },
AnyValue {
stringValue: '/Users/trentm/el/elastic-otel-node2/packages/mockotlpserver/examples/simple-http-request.js'
}
]
}
}
},
KeyValue { key: 'process.runtime.version', value: AnyValue { stringValue: '18.18.2' } },
KeyValue { key: 'process.runtime.name', value: AnyValue { stringValue: 'nodejs' } },
KeyValue { key: 'process.runtime.description', value: AnyValue { stringValue: 'Node.js' } },
KeyValue {
key: 'process.command',
value: AnyValue {
stringValue: '/Users/trentm/el/elastic-otel-node2/packages/mockotlpserver/examples/simple-http-request.js'
}
},
KeyValue { key: 'process.owner', value: AnyValue { stringValue: 'trentm' } }
],
droppedAttributesCount: 0
}
}
]
}
```

</details>


## Different OTLP protocols

By default the NodeSDK uses the `OTLP/proto` protocol. The other flavours of OTLP
are supported by `mockotlpserver` as well. Use the `OTEL_EXPORTER_OTLP_PROTOCOL`
to tell the NodeSDK to use a different protocol:

```
OTEL_EXPORTER_OTLP_PROTOCOL=http/json node -r ./telemetry.js simple-http-request.js
OTEL_EXPORTER_OTLP_PROTOCOL=grpc node -r ./telemetry.js simple-http-request.js
```

If you look carefully, you can see some differences in the representation of some fields
(startTimeUnixNano, traceId, spanId, etc.)

<!--
Try all the protocols:
for flav in http/proto http/json grpc; do OTEL_EXPORTER_OTLP_PROTOCOL=$flav node -r ./telemetry.js simple-http-request.js; done
-->


## Different mockotlpserver output modes

```
node lib/mockotlpserver.js -o json
node lib/mockotlpserver.js -o json2
```

Two other output modes are `json` (0-space indentation) and `json2` (2-space
indentation). These emit a JSON representation of each request, with some
normalization applied:

- `attributes` are converted to a mapping for brevity
- `traceId`, `spanId`, `parentSpanId` are converted to a hex value
- `startTimeUnixNano`, `endTimeUnixNano` are converted to a string of a 64-bit integer
(JavaScript's JSON.stringify cannot handle large 64-bit integers, so using
Number can lose precision.)
Loading

0 comments on commit 73432b7

Please sign in to comment.