Skip to content

Commit 3d31f21

Browse files
authored
Add support for the @launchdarkly/observability-node plugin. (#84)
## Summary Initial port of the SDK to a plugin. ## How did you test this change? <!-- Frontend - Leave a screencast or a screenshot to visually describe the changes. --> ## Are there any deployment considerations? <!-- Backend - Do we need to consider migrations or backfilling data? --> ## Does this work require review from our design team? <!-- Request review from julian-highlight / our design team -->
1 parent 491a594 commit 3d31f21

25 files changed

+1726
-0
lines changed

.changeset/dirty-streets-live.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@launchdarkly/observability-node': minor
3+
---
4+
5+
Add prerelease version of Node.js Server-Side Observability Plugin
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Copyright 2025 Catamorphic, Co.
2+
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
7+
http://www.apache.org/licenses/LICENSE-2.0
8+
9+
Unless required by applicable law or agreed to in writing, software
10+
distributed under the License is distributed on an "AS IS" BASIS,
11+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
See the License for the specific language governing permissions and
13+
limitations under the License.
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# LaunchDarkly Node.js Server-Side Obvervability Plugin
2+
3+
[![NPM][o11y-sdk-npm-badge]][o11y-sdk-npm-link]
4+
[![Actions Status][o11y-sdk-ci-badge]][o11y-sdk-ci]
5+
[![NPM][o11y-sdk-dm-badge]][o11y-sdk-npm-link]
6+
[![NPM][o11y-sdk-dt-badge]][o11y-sdk-npm-link]
7+
8+
# Early Access Preview️
9+
10+
> [!CAUTION]
11+
> This library is a alpha version and should not be considered ready for production use while this message is visible.
12+
13+
## Install
14+
15+
Install the package
16+
```shell
17+
# npm
18+
npm i @launchdarkly/observability-node
19+
20+
# yarn
21+
yarn add @launchdarkly/observability-node
22+
```
23+
24+
Update your web app entrypoint.
25+
```TypeScript
26+
import { init } from '@launchdarkly/node-server-sdk'
27+
import Observability, { LDObserve } from '@launchdarkly/observability'
28+
29+
const client = init(
30+
'sdk-key',
31+
{
32+
plugins: [
33+
new Observability(),
34+
],
35+
},
36+
)
37+
38+
```
39+
40+
## Getting started
41+
42+
Refer to the [SDK documentation](https://launchdarkly.com/docs/sdk/server-side/node-js#get-started) for instructions on getting started with using the SDK.
43+
44+
## Verifying SDK build provenance with the SLSA framework
45+
46+
LaunchDarkly uses the [SLSA framework](https://slsa.dev/spec/v1.0/about) (Supply-chain Levels for Software Artifacts) to help developers make their supply chain more secure by ensuring the authenticity and build integrity of our published SDK packages. To learn more, see the [provenance guide](PROVENANCE.md).
47+
48+
## About LaunchDarkly
49+
50+
- LaunchDarkly Observability provies a way to collect and send errors, logs, traces to LaunchDarkly. Correlate latency or exceptions with your releases to safely ship code.
51+
- LaunchDarkly is a continuous delivery platform that provides feature flags as a service and allows developers to iterate quickly and safely. We allow you to easily flag your features and manage them from the LaunchDarkly dashboard. With LaunchDarkly, you can:
52+
- Roll out a new feature to a subset of your users (like a group of users who opt-in to a beta tester group), gathering feedback and bug reports from real-world use cases.
53+
- Gradually roll out a feature to an increasing percentage of users, and track the effect that the feature has on key metrics (for instance, how likely is a user to complete a purchase if they have feature A versus feature B?).
54+
- Turn off a feature that you realize is causing performance problems in production, without needing to re-deploy, or even restart the application with a changed configuration file.
55+
- Grant access to certain features based on user attributes, like payment plan (eg: users on the ‘gold’ plan get access to more features than users in the ‘silver’ plan).
56+
- Disable parts of your application to facilitate maintenance, without taking everything offline.
57+
- LaunchDarkly provides feature flag SDKs for a wide variety of languages and technologies. Read [our documentation](https://docs.launchdarkly.com/sdk) for a complete list.
58+
- Explore LaunchDarkly
59+
- [launchdarkly.com](https://www.launchdarkly.com/ 'LaunchDarkly Main Website') for more information
60+
- [docs.launchdarkly.com](https://docs.launchdarkly.com/ 'LaunchDarkly Documentation') for our documentation and SDK reference guides
61+
- [apidocs.launchdarkly.com](https://apidocs.launchdarkly.com/ 'LaunchDarkly API Documentation') for our API documentation
62+
- [blog.launchdarkly.com](https://blog.launchdarkly.com/ 'LaunchDarkly Blog Documentation') for the latest product updates
63+
64+
[o11y-sdk-ci-badge]: https://github.com/launchdarkly/observability-sdk/actions/workflows/turbo.yml/badge.svg
65+
[o11y-sdk-ci]: https://github.com/launchdarkly/observability-sdk/actions/workflows/turbo.yml
66+
[o11y-sdk-npm-badge]: https://img.shields.io/npm/v/@launchdarkly/observability-node.svg?style=flat-square
67+
[o11y-sdk-npm-link]: https://www.npmjs.com/package/@launchdarkly/observability-node
68+
[o11y-sdk-dm-badge]: https://img.shields.io/npm/dm/@launchdarkly/observability-node.svg?style=flat-square
69+
[o11y-sdk-dt-badge]: https://img.shields.io/npm/dt/@launchdarkly/observability-node.svg?style=flat-square
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
{
2+
"name": "@launchdarkly/observability-node",
3+
"version": "0.0.1",
4+
"license": "Apache-2.0",
5+
"repository": {
6+
"type": "git",
7+
"url": "https://github.com/launchdarkly/observability-sdk.git"
8+
},
9+
"scripts": {
10+
"typegen": "tsc -d --emitDeclarationOnly",
11+
"dev": "yarn build --watch",
12+
"build": "rollup --config rollup.config.mjs",
13+
"test": "vitest run"
14+
},
15+
"main": "./dist/index.cjs",
16+
"module": "./dist/index.js",
17+
"types": "./dist/index.d.ts",
18+
"exports": {
19+
"types": "./dist/index.d.ts",
20+
"default": "./dist/index.cjs"
21+
},
22+
"files": [
23+
"dist"
24+
],
25+
"publishConfig": {
26+
"access": "public"
27+
},
28+
"dependencies": {
29+
"@launchdarkly/node-server-sdk-otel": "^1.2.2",
30+
"@prisma/instrumentation": ">=5.0.0",
31+
"require-in-the-middle": "^7.4.0"
32+
},
33+
"peerDependencies": {
34+
"@launchdarkly/js-server-sdk-common": "^2.15.2",
35+
"@launchdarkly/node-server-sdk": "^9.9.2"
36+
},
37+
"devDependencies": {
38+
"@launchdarkly/node-server-sdk": "^9.9.2",
39+
"@opentelemetry/api": "^1.9.0",
40+
"@opentelemetry/api-logs": "^0.57.2",
41+
"@opentelemetry/auto-instrumentations-node": "^0.56.0",
42+
"@opentelemetry/core": "^1.30.1",
43+
"@opentelemetry/exporter-jaeger": "^1.30.1",
44+
"@opentelemetry/exporter-logs-otlp-http": "^0.57.2",
45+
"@opentelemetry/exporter-metrics-otlp-http": "^0.57.2",
46+
"@opentelemetry/exporter-trace-otlp-http": "^0.57.2",
47+
"@opentelemetry/instrumentation": "^0.57.2",
48+
"@opentelemetry/otlp-exporter-base": "^0.57.2",
49+
"@opentelemetry/otlp-transformer": "^0.57.2",
50+
"@opentelemetry/resources": "^1.30.1",
51+
"@opentelemetry/sdk-logs": "^0.57.2",
52+
"@opentelemetry/sdk-node": "^0.57.2",
53+
"@opentelemetry/sdk-trace-base": "^1.30.1",
54+
"@opentelemetry/semantic-conventions": "^1.30.0",
55+
"@opentelemetry/winston-transport": "^0.10.0",
56+
"@rollup/plugin-commonjs": "^28.0.2",
57+
"@rollup/plugin-json": "^6.1.0",
58+
"@rollup/plugin-node-resolve": "^16.0.0",
59+
"@rollup/plugin-terser": "^0.4.4",
60+
"@rollup/plugin-typescript": "^12.1.2",
61+
"@types/lru-cache": "^7.10.10",
62+
"@types/node": "^14.18.63",
63+
"encoding": "^0.1.13",
64+
"rollup": "^4.34.8",
65+
"typescript": "^5.0.4",
66+
"vitest": "^2.1.3"
67+
}
68+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import commonjs from '@rollup/plugin-commonjs'
2+
import json from '@rollup/plugin-json'
3+
import resolve from '@rollup/plugin-node-resolve'
4+
import terser from '@rollup/plugin-terser'
5+
import typescript from '@rollup/plugin-typescript'
6+
7+
/** @type {import('rollup').RollupOptions} */
8+
const config = {
9+
input: 'src/index.ts',
10+
context: 'global',
11+
external: [
12+
'require-in-the-middle',
13+
'@launchdarkly/node-server-sdk',
14+
'@launchdarkly/node-server-sdk-otel',
15+
],
16+
plugins: [
17+
json(),
18+
commonjs({
19+
// required for @opentelemetry/resources which pretends to be an ESM build while using dynamic `require()`
20+
transformMixedEsModules: true,
21+
}),
22+
resolve({
23+
preferBuiltins: true,
24+
// avoid bundling require-in-the-middle for next.js compatibility
25+
resolveOnly: (module) =>
26+
!module.includes('require-in-the-middle') &&
27+
!module.includes('@launchdarkly/node-server-sdk') &&
28+
!module.includes('@launchdarkly/node-server-sdk-otel'),
29+
}),
30+
typescript(),
31+
terser(),
32+
],
33+
output: [
34+
{
35+
file: 'dist/index.js',
36+
format: 'es',
37+
sourcemap: true,
38+
exports: 'auto',
39+
},
40+
{
41+
file: 'dist/index.cjs',
42+
format: 'cjs',
43+
sourcemap: true,
44+
exports: 'auto',
45+
},
46+
],
47+
treeshake: 'safest',
48+
}
49+
50+
export default config
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export declare interface Metric {
2+
name: string
3+
value: number
4+
tags?: { name: string; value: string }[]
5+
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import {
2+
Attributes,
3+
Context,
4+
Span as OtelSpan,
5+
SpanOptions,
6+
} from '@opentelemetry/api'
7+
import { RequestContext } from './RequestContext'
8+
import { Metric } from './Metric'
9+
import { Headers, IncomingHttpHeaders } from './headers'
10+
11+
export interface Observe {
12+
/**
13+
* Record arbitrary metric values via as a Gauge.
14+
* A Gauge records any point-in-time measurement, such as the current CPU utilization %.
15+
* Values with the same metric name and attributes are aggregated via the OTel SDK.
16+
* See https://opentelemetry.io/docs/specs/otel/metrics/data-model/ for more details.
17+
*/
18+
recordMetric(metric: Metric): void
19+
20+
/**
21+
* Record arbitrary metric values via as a Counter.
22+
* A Counter efficiently records an increment in a metric, such as number of cache hits.
23+
* Values with the same metric name and attributes are aggregated via the OTel SDK.
24+
* See https://opentelemetry.io/docs/specs/otel/metrics/data-model/ for more details.
25+
*/
26+
recordCount(metric: Metric): void
27+
28+
/**
29+
* Record arbitrary metric values via as a Counter.
30+
* A Counter efficiently records an increment in a metric, such as number of cache hits.
31+
* Values with the same metric name and attributes are aggregated via the OTel SDK.
32+
* See https://opentelemetry.io/docs/specs/otel/metrics/data-model/ for more details.
33+
*/
34+
recordIncr(metric: Omit<Metric, 'value'>): void
35+
36+
/**
37+
* Record arbitrary metric values via as a Histogram.
38+
* A Histogram efficiently records near-by point-in-time measurement into a bucketed aggregate.
39+
* Values with the same metric name and attributes are aggregated via the OTel SDK.
40+
* See https://opentelemetry.io/docs/specs/otel/metrics/data-model/ for more details.
41+
*/
42+
recordHistogram(metric: Metric): void
43+
44+
/**
45+
* Record arbitrary metric values via as a UpDownCounter.
46+
* A UpDownCounter efficiently records an increment or decrement in a metric, such as number of paying customers.
47+
* Values with the same metric name and attributes are aggregated via the OTel SDK.
48+
* See https://opentelemetry.io/docs/specs/otel/metrics/data-model/ for more details.
49+
*/
50+
recordUpDownCounter(metric: Metric): void
51+
52+
/**
53+
* Logs a message with optional session and request context
54+
*/
55+
recordLog(
56+
message: any,
57+
level: string,
58+
secureSessionId?: string | undefined,
59+
requestId?: string | undefined,
60+
metadata?: Attributes,
61+
): void
62+
63+
/**
64+
* Record an error.
65+
*/
66+
recordError(
67+
error: Error,
68+
secureSessionId: string | undefined,
69+
requestId: string | undefined,
70+
metadata?: Attributes,
71+
options?: { span: OtelSpan },
72+
): void
73+
74+
/**
75+
* Flushes all pending telemetry data
76+
*/
77+
flush(): Promise<void>
78+
79+
/**
80+
* Sets attributes on the active span
81+
*/
82+
setAttributes(attributes: Attributes): void
83+
84+
/**
85+
* Parses headers to extract highlight context
86+
*/
87+
parseHeaders(headers: Headers | IncomingHttpHeaders): RequestContext
88+
89+
/**
90+
* Runs a callback with headers context and returns the result
91+
*/
92+
runWithHeaders<T>(
93+
name: string,
94+
headers: Headers | IncomingHttpHeaders,
95+
cb: (span: OtelSpan) => T | Promise<T>,
96+
options?: SpanOptions,
97+
): Promise<T>
98+
99+
/**
100+
* Starts a span with headers context
101+
*/
102+
startWithHeaders<T>(
103+
spanName: string,
104+
headers: Headers | IncomingHttpHeaders,
105+
options?: SpanOptions,
106+
): { span: OtelSpan; ctx: Context }
107+
108+
/**
109+
* Stops the observability client and flushes remaining data
110+
*/
111+
stop(): Promise<void>
112+
}

0 commit comments

Comments
 (0)