@kittl/observability-node 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,174 @@
1
+ # Node Observability
2
+
3
+ Shared Node.js observability for Kittl services and workers.
4
+
5
+ `@kittl/observability-node` provides a single entry point for OpenTelemetry tracing and metrics.
6
+
7
+ ## How to use
8
+
9
+ Create `telemetry.ts` file and make sure it is imported into your entrypoint:
10
+
11
+ ```typescript
12
+ import { KittlNodeTelemetry } from '@kittl/observability-node';
13
+
14
+ const telemetry = new KittlNodeTelemetry({
15
+ serviceName: 'api',
16
+ serviceVersion: process.env.RELEASE_SHA,
17
+ owner: 'developers',
18
+ });
19
+ telemetry.start();
20
+ ```
21
+
22
+ With default parameters this sets up Node auto-instrumentation and exports both metrics and traces. Metrics will be exposed on port `9464` under `/metrics` path by default. If you use `@kittl/logger`, logs will be correlated with traces automatically.
23
+
24
+ It is recommended to shut down the instance gracefully based on `SIGTERM` signal:
25
+
26
+ ```typescript
27
+ process.on('SIGTERM', () => {
28
+ telemetry
29
+ .shutdown()
30
+ .then(
31
+ () => logger.info('SDK shut down successfully'),
32
+ (err) => logger.error('Error shutting down SDK', err)
33
+ )
34
+ .finally(() => process.exit(0));
35
+ });
36
+ ```
37
+
38
+ Node auto-instrumentations can be fine-tuned using `nodeAutoInstrumentations` parameter:
39
+
40
+ ```typescript
41
+ new KittlNodeTelemetry({
42
+ serviceName: 'api',
43
+ nodeAutoInstrumentations: {
44
+ '@opentelemetry/instrumentation-express': {
45
+ ignoreLayersType: [ExpressLayerType.MIDDLEWARE, ExpressLayerType.ROUTER],
46
+ },
47
+ },
48
+ });
49
+ ```
50
+
51
+ NOTE: For each auto instrumentation configuration, only specified parameters will be overridden. Remaining parameters will be set to default values.
52
+
53
+ > Check [supported-instrumentations](https://github.com/open-telemetry/opentelemetry-js-contrib/tree/main/packages/) for information on what is included.
54
+
55
+ Additional instrumentations can be added via `additionalInstrumentations` parameter:
56
+
57
+ ```typescript
58
+ import { PrismaInstrumentation } from '@prisma/instrumentation';
59
+
60
+ new KittlNodeTelemetry({
61
+ serviceName: 'api',
62
+ additionalInstrumentations: [new PrismaInstrumentation()],
63
+ });
64
+ ```
65
+
66
+ ## Kubernetes configuration
67
+
68
+ To make sure that service metrics are collected and traces are exported, following minimal `values.yaml` configuration is required:
69
+
70
+ ```yaml
71
+ env:
72
+ - name: NODE_IP
73
+ valueFrom:
74
+ fieldRef:
75
+ fieldPath: status.hostIP
76
+ - name: OTEL_EXPORTER_OTLP_ENDPOINT
77
+ value: http://$(NODE_IP):4318
78
+ deployment:
79
+ metrics:
80
+ enabled: true
81
+ port: 9464 # change if different port is used
82
+ path: /metrics
83
+ ```
84
+
85
+ In addition following environment variables are recommended to be set via configmap:
86
+
87
+ ```yaml
88
+ OTEL_LOG_LEVEL: warn
89
+ # use new stable HTTP conventions
90
+ OTEL_SEMCONV_STABILITY_OPT_IN: http
91
+ ```
92
+
93
+ ## User journeys
94
+
95
+ This package provides abstraction for tracking user journeys in a standardized way via `Journey` class. It supports recording step transitions as a counter metric, as well as recording step durations into a histogram.
96
+
97
+ Steps are particular operations inside the journey. For example, AI generation journey might contain separate steps to charge the user, generate image and refund the user in case of error. Each step has a particular outcome - for example, generation might fail with an error, be successful or it might time out. For more information on this topic see [Business Metrics Taxonomy](https://app.notion.com/p/kittl/Business-Metrics-Taxonomy-35985ae43d478142bd1fef4b5e86a02b)
98
+
99
+ Here's an example of tracking `generation` step in `ai_generation` journey using `recordStep` method:
100
+
101
+ ```typescript
102
+ import { Journey, ATTR_OUTCOME, ATTR_ERROR_CATEGORY } from '@kittl/observability-node';
103
+
104
+ const aiGenerationJourney = new Journey('ai_generation');
105
+
106
+ function generateAiImage(...) {
107
+ try {
108
+ /** some business logic */
109
+ } catch {
110
+ aiGenerationJourney.recordStep('generation', {
111
+ [ATTR_OUTCOME]: 'failure',
112
+ [ATTR_ERROR_CATEGORY]: 'generation_failed',
113
+ });
114
+ return;
115
+ }
116
+ aiGenerationJourney.recordStep('generation', {
117
+ [ATTR_OUTCOME]: 'success',
118
+ });
119
+ }
120
+ ```
121
+
122
+ > NOTE: Error category is always tracked, but defaults to `none` when it is not specified.
123
+
124
+ By default `Journey` only allows passing built-in `ATTR_OUTCOME` and `ATTR_ERROR_CATEGORY` attributes. To include additional attributes, they should be fined as an `interface` and passed to `Journey` as a generic type argument:
125
+
126
+ ```typescript
127
+ import { ATTR_PROVIDER } from '@kittl/observability-node';
128
+
129
+ const ATTR_CONTENT_TYPE = 'content.type';
130
+
131
+ interface AiGenerationJourneyAttributes {
132
+ [ATTR_PROVIDER]?: 'nano-banana' | 'gemini' | 'stripe';
133
+ [ATTR_CONTENT_TYPE]?: 'image' | 'video';
134
+ }
135
+
136
+ const aiGenerationJourney = new Journey<AiGenerationJourneyAttributes>(
137
+ 'ai_generation'
138
+ );
139
+ aiGenerationJourney.recordStep('generation', {
140
+ [ATTR_OUTCOME]: 'success',
141
+ [ATTR_PROVIDER]: 'nano-banana',
142
+ [ATTR_CONTENT_TYPE]: 'image',
143
+ });
144
+ ```
145
+
146
+ In cases where we are also interested in duration of a step, it is possible to also track step duration via `recordStepWithDuration` method. It does same as what `recordStep` does, but also records duration measurement in a histogram. Here's an example of tracking a different step in `ai_generation` journey:
147
+
148
+ ```typescript
149
+ function chargeUserForGeneration(...) {
150
+ const startTime = performance.now();
151
+ /** some business logic */
152
+ aiGenerationJourney.recordStepWithDuration(
153
+ 'charge',
154
+ (performance.now() - startTime) / 1000, // convert to seconds
155
+ {
156
+ [ATTR_OUTCOME]: 'success',
157
+ [ATTR_PROVIDER]: 'stripe'
158
+ }
159
+ ),
160
+ }
161
+ ```
162
+
163
+ > NOTE: This example omits error handling for simplicity, but it is necessary when tracking durations in production code.
164
+
165
+ As this pattern of tracking start time and subtracting it from current time is so common, `Journey` class provides a useful `createTimer` method to simplify it:
166
+
167
+ ```typescript
168
+ const timer = aiGenerationJourney.createTimer();
169
+ /** some businees logic */
170
+ timer.recordStepWithDuration('charge', {
171
+ [ATTR_OUTCOME]: 'success',
172
+ [ATTR_PROVIDER]: 'stripe',
173
+ });
174
+ ```
@@ -0,0 +1,42 @@
1
+ /** Owner of the service. */
2
+ export declare const ATTR_OWNER = "owner";
3
+ /** Name of the journey. */
4
+ export declare const ATTR_JOURNEY = "journey";
5
+ /** Name of the journey step. */
6
+ export declare const ATTR_STEP = "step";
7
+ /** Journey step outcome. */
8
+ export declare const ATTR_OUTCOME = "outcome";
9
+ /** Step completed as expected. */
10
+ export declare const OUTCOME_VALUE_SUCCESS = "success";
11
+ /** Unexpected technical or dependency failure. */
12
+ export declare const OUTCOME_VALUE_FAILURE = "failure";
13
+ /** Expected product/business block */
14
+ export declare const OUTCOME_VALUE_BLOCKED = "blocked";
15
+ /** Step exceeded the defined completion window. */
16
+ export declare const OUTCOME_VALUE_TIMEOUT = "timeout";
17
+ /** User or system explicitly cancelled the operation */
18
+ export declare const OUTCOME_VALUE_CANCELLED = "cancelled";
19
+ /**
20
+ * User left/cancelled before completion and this is observable.
21
+ *
22
+ * Use only when the client/RUM path can observe abandonment;
23
+ * do not infer per-user abandonment from missing backend completion alone.
24
+ */
25
+ export declare const OUTCOME_VALUE_ABANDONED = "abandoned";
26
+ /** Temporary fallback when classification is missing. */
27
+ export declare const OUTCOME_VALUE_UNKNOWN = "unknown";
28
+ export type OutcomeValue = typeof OUTCOME_VALUE_SUCCESS | typeof OUTCOME_VALUE_FAILURE | typeof OUTCOME_VALUE_BLOCKED | typeof OUTCOME_VALUE_TIMEOUT | typeof OUTCOME_VALUE_CANCELLED | typeof OUTCOME_VALUE_ABANDONED | typeof OUTCOME_VALUE_UNKNOWN;
29
+ /** Journey step error category. */
30
+ export declare const ATTR_ERROR_CATEGORY = "error.category";
31
+ /** Use for successful outcomes. */
32
+ export declare const ERROR_CATEGORY_VALUE_NONE = "none";
33
+ /** User-facing surface where the journey step is happening. */
34
+ export declare const ATTR_SURFACE = "surface";
35
+ /** Subcomponent which is responsible for the journey step. */
36
+ export declare const ATTR_COMPONENT = "component";
37
+ /** External provider which is responsible for the journey step. */
38
+ export declare const ATTR_PROVIDER = "provider";
39
+ /** Bounded dependency being called. */
40
+ export declare const ATTR_DEPENDENCY = "dependency";
41
+ /** General fallback for undefined and null values. */
42
+ export declare const VALUE_UNKNOWN = "unknown";
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ // Resource attribute conventions.
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.VALUE_UNKNOWN = exports.ATTR_DEPENDENCY = exports.ATTR_PROVIDER = exports.ATTR_COMPONENT = exports.ATTR_SURFACE = exports.ERROR_CATEGORY_VALUE_NONE = exports.ATTR_ERROR_CATEGORY = exports.OUTCOME_VALUE_UNKNOWN = exports.OUTCOME_VALUE_ABANDONED = exports.OUTCOME_VALUE_CANCELLED = exports.OUTCOME_VALUE_TIMEOUT = exports.OUTCOME_VALUE_BLOCKED = exports.OUTCOME_VALUE_FAILURE = exports.OUTCOME_VALUE_SUCCESS = exports.ATTR_OUTCOME = exports.ATTR_STEP = exports.ATTR_JOURNEY = exports.ATTR_OWNER = void 0;
5
+ /** Owner of the service. */
6
+ exports.ATTR_OWNER = 'owner';
7
+ // Journey metric attribute conventions.
8
+ /** Name of the journey. */
9
+ exports.ATTR_JOURNEY = 'journey';
10
+ /** Name of the journey step. */
11
+ exports.ATTR_STEP = 'step';
12
+ /** Journey step outcome. */
13
+ exports.ATTR_OUTCOME = 'outcome';
14
+ /** Step completed as expected. */
15
+ exports.OUTCOME_VALUE_SUCCESS = 'success';
16
+ /** Unexpected technical or dependency failure. */
17
+ exports.OUTCOME_VALUE_FAILURE = 'failure';
18
+ /** Expected product/business block */
19
+ exports.OUTCOME_VALUE_BLOCKED = 'blocked';
20
+ /** Step exceeded the defined completion window. */
21
+ exports.OUTCOME_VALUE_TIMEOUT = 'timeout';
22
+ /** User or system explicitly cancelled the operation */
23
+ exports.OUTCOME_VALUE_CANCELLED = 'cancelled';
24
+ /**
25
+ * User left/cancelled before completion and this is observable.
26
+ *
27
+ * Use only when the client/RUM path can observe abandonment;
28
+ * do not infer per-user abandonment from missing backend completion alone.
29
+ */
30
+ exports.OUTCOME_VALUE_ABANDONED = 'abandoned';
31
+ /** Temporary fallback when classification is missing. */
32
+ exports.OUTCOME_VALUE_UNKNOWN = 'unknown';
33
+ /** Journey step error category. */
34
+ exports.ATTR_ERROR_CATEGORY = 'error.category';
35
+ /** Use for successful outcomes. */
36
+ exports.ERROR_CATEGORY_VALUE_NONE = 'none';
37
+ /** User-facing surface where the journey step is happening. */
38
+ exports.ATTR_SURFACE = 'surface';
39
+ /** Subcomponent which is responsible for the journey step. */
40
+ exports.ATTR_COMPONENT = 'component';
41
+ /** External provider which is responsible for the journey step. */
42
+ exports.ATTR_PROVIDER = 'provider';
43
+ /** Bounded dependency being called. */
44
+ exports.ATTR_DEPENDENCY = 'dependency';
45
+ /** General fallback for undefined and null values. */
46
+ exports.VALUE_UNKNOWN = 'unknown';
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Filters out null/undefined values from an object.
3
+ * @param object - Object to filter.
4
+ * @returns New object with null/undefined values removed.
5
+ */
6
+ export declare function dropNullUndefinedEntries<T extends object>(object: T): T;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.dropNullUndefinedEntries = dropNullUndefinedEntries;
4
+ /**
5
+ * Filters out null/undefined values from an object.
6
+ * @param object - Object to filter.
7
+ * @returns New object with null/undefined values removed.
8
+ */
9
+ function dropNullUndefinedEntries(object) {
10
+ return Object.fromEntries(Object.entries(object).filter(([, value]) => value != null));
11
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Implements a singleton pattern, initializing the instance only when it is accessed for the first time.
3
+ * @param create - A function that creates the instance.
4
+ * @returns Function to get the singleton instance.
5
+ */
6
+ export default function singleton<T>(create: () => T): () => T;
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = singleton;
4
+ /**
5
+ * Implements a singleton pattern, initializing the instance only when it is accessed for the first time.
6
+ * @param create - A function that creates the instance.
7
+ * @returns Function to get the singleton instance.
8
+ */
9
+ function singleton(create) {
10
+ let instance = null;
11
+ return () => {
12
+ if (!instance) {
13
+ instance = create();
14
+ }
15
+ return instance;
16
+ };
17
+ }
@@ -0,0 +1,3 @@
1
+ export * from './conventions';
2
+ export { Journey } from './journey';
3
+ export { KittlNodeTelemetry } from './telemetry';
package/dist/index.js ADDED
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.KittlNodeTelemetry = exports.Journey = void 0;
18
+ __exportStar(require("./conventions"), exports);
19
+ var journey_1 = require("./journey");
20
+ Object.defineProperty(exports, "Journey", { enumerable: true, get: function () { return journey_1.Journey; } });
21
+ var telemetry_1 = require("./telemetry");
22
+ Object.defineProperty(exports, "KittlNodeTelemetry", { enumerable: true, get: function () { return telemetry_1.KittlNodeTelemetry; } });
@@ -0,0 +1,33 @@
1
+ import { ATTR_ERROR_CATEGORY, ATTR_OUTCOME, type OutcomeValue } from './conventions';
2
+ type BaseJourneyAttributes = {
3
+ [ATTR_OUTCOME]: OutcomeValue;
4
+ [ATTR_ERROR_CATEGORY]?: string;
5
+ };
6
+ interface JourneyTimer<TAttributes> {
7
+ recordStepWithDuration(step: string, attributes: TAttributes): void;
8
+ }
9
+ /** Manages metrics for a specific journey. */
10
+ export declare class Journey<TAttributes extends object = BaseJourneyAttributes> {
11
+ private readonly name;
12
+ constructor(name: string);
13
+ /**
14
+ * Record transition for a particular step in the journey.
15
+ * @param step - The name of the step.
16
+ * @param attributes - Additional attributes to add to the metric.
17
+ */
18
+ recordStep(step: string, attributes: TAttributes & BaseJourneyAttributes): void;
19
+ /**
20
+ * Record transition for a particular step in the journey, along with its duration.
21
+ * @param step - The name of the step.
22
+ * @param durationSeconds - How long the step took in seconds.
23
+ * @param attributes - Additional attributes to add to the metric.
24
+ */
25
+ recordStepWithDuration(step: string, durationSeconds: number, attributes: TAttributes & BaseJourneyAttributes): void;
26
+ /**
27
+ * Helper to automatically time elapsed duration and record it in the appropriate metric.
28
+ * @returns A timer object that is automatically started.
29
+ */
30
+ createTimer(): JourneyTimer<TAttributes & BaseJourneyAttributes>;
31
+ private normalizeStepAttributes;
32
+ }
33
+ export {};
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.Journey = void 0;
7
+ const api_1 = require("@opentelemetry/api");
8
+ const conventions_1 = require("./conventions");
9
+ const dropNullUndefinedEntries_1 = require("./helpers/dropNullUndefinedEntries");
10
+ const singleton_1 = __importDefault(require("./helpers/singleton"));
11
+ const logger_1 = require("./logger");
12
+ const log = logger_1.logger.child('journeys');
13
+ // Lazy initialize metrics to make sure that SDK is started before we use them
14
+ const getJourneysMeter = (0, singleton_1.default)(() => api_1.metrics.getMeter('kittl.journeys'));
15
+ const getJourneyStepCounter = (0, singleton_1.default)(() => getJourneysMeter().createCounter('journey.step', {
16
+ description: 'Count of journey step transitions',
17
+ unit: '{event}',
18
+ }));
19
+ const getJourneyStepDurationHistogram = (0, singleton_1.default)(() => getJourneysMeter().createHistogram('journey.step.duration', {
20
+ description: 'Journey step durations',
21
+ unit: 's',
22
+ advice: {
23
+ explicitBucketBoundaries: [
24
+ 0.1, 0.25, 0.5, 1, 2, 5, 10, 30, 60, 120, 300, 600, 1200,
25
+ ],
26
+ },
27
+ }));
28
+ /** Manages metrics for a specific journey. */
29
+ class Journey {
30
+ constructor(name) {
31
+ this.name = name;
32
+ }
33
+ /**
34
+ * Record transition for a particular step in the journey.
35
+ * @param step - The name of the step.
36
+ * @param attributes - Additional attributes to add to the metric.
37
+ */
38
+ recordStep(step, attributes) {
39
+ getJourneyStepCounter().add(1, this.normalizeStepAttributes(step, attributes));
40
+ }
41
+ /**
42
+ * Record transition for a particular step in the journey, along with its duration.
43
+ * @param step - The name of the step.
44
+ * @param durationSeconds - How long the step took in seconds.
45
+ * @param attributes - Additional attributes to add to the metric.
46
+ */
47
+ recordStepWithDuration(step, durationSeconds, attributes) {
48
+ if (durationSeconds < 0 || !Number.isFinite(durationSeconds)) {
49
+ log.error('Error recording journey step duration', new Error('Invalid duration provided'), {
50
+ durationSeconds,
51
+ journey: this.name,
52
+ step,
53
+ });
54
+ return;
55
+ }
56
+ this.recordStep(step, attributes);
57
+ getJourneyStepDurationHistogram().record(durationSeconds, this.normalizeStepAttributes(step, attributes));
58
+ }
59
+ /**
60
+ * Helper to automatically time elapsed duration and record it in the appropriate metric.
61
+ * @returns A timer object that is automatically started.
62
+ */
63
+ createTimer() {
64
+ const start = performance.now();
65
+ const getDuration = () => (performance.now() - start) / 1000;
66
+ return {
67
+ recordStepWithDuration: (step, attributes) => this.recordStepWithDuration(step, getDuration(), attributes),
68
+ };
69
+ }
70
+ normalizeStepAttributes(step, attributes) {
71
+ return {
72
+ ...(0, dropNullUndefinedEntries_1.dropNullUndefinedEntries)(attributes),
73
+ [conventions_1.ATTR_JOURNEY]: this.name,
74
+ [conventions_1.ATTR_STEP]: step,
75
+ // error category should always be present
76
+ [conventions_1.ATTR_ERROR_CATEGORY]: attributes[conventions_1.ATTR_ERROR_CATEGORY] ?? conventions_1.ERROR_CATEGORY_VALUE_NONE,
77
+ };
78
+ }
79
+ }
80
+ exports.Journey = Journey;
@@ -0,0 +1,2 @@
1
+ import { ApiLogger } from '@repo/logger';
2
+ export declare const logger: ApiLogger;
package/dist/logger.js ADDED
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.logger = void 0;
4
+ const logger_1 = require("@repo/logger");
5
+ exports.logger = new logger_1.ApiLogger();
@@ -0,0 +1,31 @@
1
+ import { type InstrumentationConfigMap } from '@opentelemetry/auto-instrumentations-node';
2
+ import type { Instrumentation } from '@opentelemetry/instrumentation';
3
+ import { NodeSDK, type NodeSDKConfiguration } from '@opentelemetry/sdk-node';
4
+ interface KittlNodeTelemetryOptions {
5
+ serviceName: string;
6
+ serviceVersion?: string;
7
+ owner?: string;
8
+ metricsEnabled?: boolean;
9
+ tracesEnabled?: boolean;
10
+ metricsPort?: number;
11
+ nodeAutoInstrumentations?: InstrumentationConfigMap;
12
+ additionalInstrumentations?: Instrumentation[];
13
+ }
14
+ export declare class KittlNodeTelemetry {
15
+ readonly sdk: NodeSDK;
16
+ /**
17
+ * @param options.serviceName - The name of the service (required).
18
+ * @param options.serviceVersion - The version of the service (optional).
19
+ * @param options.owner - The owner of the service (optional).
20
+ * @param options.metricsEnabled - Whether to enable metrics (default: true).
21
+ * @param options.tracesEnabled - Whether to enable tracing (default: true).
22
+ * @param options.metricsPort - The port to use for the metrics exporter (default: 9464).
23
+ * @param options.nodeAutoInstrumentations - Override the default auto-instrumentations.
24
+ * @param options.additionalInstrumentations - Additional instrumentations to add to the SDK.
25
+ */
26
+ constructor(options: KittlNodeTelemetryOptions);
27
+ instantiateSdk(config: Partial<NodeSDKConfiguration>): NodeSDK;
28
+ start(): void;
29
+ shutdown(): Promise<void>;
30
+ }
31
+ export {};
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.KittlNodeTelemetry = void 0;
4
+ const auto_instrumentations_node_1 = require("@opentelemetry/auto-instrumentations-node");
5
+ const exporter_prometheus_1 = require("@opentelemetry/exporter-prometheus");
6
+ const exporter_trace_otlp_http_1 = require("@opentelemetry/exporter-trace-otlp-http");
7
+ const instrumentation_express_1 = require("@opentelemetry/instrumentation-express");
8
+ const resources_1 = require("@opentelemetry/resources");
9
+ const sdk_node_1 = require("@opentelemetry/sdk-node");
10
+ const semantic_conventions_1 = require("@opentelemetry/semantic-conventions");
11
+ const conventions_1 = require("./conventions");
12
+ class KittlNodeTelemetry {
13
+ /**
14
+ * @param options.serviceName - The name of the service (required).
15
+ * @param options.serviceVersion - The version of the service (optional).
16
+ * @param options.owner - The owner of the service (optional).
17
+ * @param options.metricsEnabled - Whether to enable metrics (default: true).
18
+ * @param options.tracesEnabled - Whether to enable tracing (default: true).
19
+ * @param options.metricsPort - The port to use for the metrics exporter (default: 9464).
20
+ * @param options.nodeAutoInstrumentations - Override the default auto-instrumentations.
21
+ * @param options.additionalInstrumentations - Additional instrumentations to add to the SDK.
22
+ */
23
+ constructor(options) {
24
+ const autoInstrumentations = options?.nodeAutoInstrumentations ?? {};
25
+ this.sdk = this.instantiateSdk({
26
+ resource: (0, resources_1.resourceFromAttributes)({
27
+ [semantic_conventions_1.ATTR_SERVICE_NAME]: options.serviceName,
28
+ [semantic_conventions_1.ATTR_SERVICE_VERSION]: options.serviceVersion,
29
+ [conventions_1.ATTR_OWNER]: options.owner,
30
+ }),
31
+ traceExporter: options?.tracesEnabled === false ? undefined : new exporter_trace_otlp_http_1.OTLPTraceExporter(),
32
+ metricReader: options?.metricsEnabled === false
33
+ ? undefined
34
+ : new exporter_prometheus_1.PrometheusExporter({ port: options?.metricsPort }),
35
+ instrumentations: [
36
+ (0, auto_instrumentations_node_1.getNodeAutoInstrumentations)({
37
+ ...autoInstrumentations,
38
+ // These instrumentations are disabled as they don't provide much value and add a lot of noise
39
+ '@opentelemetry/instrumentation-dns': {
40
+ enabled: false,
41
+ ...autoInstrumentations['@opentelemetry/instrumentation-dns'],
42
+ },
43
+ '@opentelemetry/instrumentation-connect': {
44
+ enabled: false,
45
+ ...autoInstrumentations['@opentelemetry/instrumentation-connect'],
46
+ },
47
+ '@opentelemetry/instrumentation-net': {
48
+ enabled: false,
49
+ ...autoInstrumentations['@opentelemetry/instrumentation-net'],
50
+ },
51
+ '@opentelemetry/instrumentation-express': {
52
+ // These layers are disabled as they don't provide much value and add a lot of noise
53
+ ignoreLayersType: [
54
+ instrumentation_express_1.ExpressLayerType.MIDDLEWARE,
55
+ instrumentation_express_1.ExpressLayerType.ROUTER,
56
+ ],
57
+ ...autoInstrumentations['@opentelemetry/instrumentation-express'],
58
+ },
59
+ '@opentelemetry/instrumentation-pino': {
60
+ // Use camelCase log keys to match other log attributes.
61
+ logKeys: {
62
+ traceId: 'traceId',
63
+ spanId: 'spanId',
64
+ traceFlags: 'traceFlags',
65
+ },
66
+ ...autoInstrumentations['@opentelemetry/instrumentation-pino'],
67
+ },
68
+ }),
69
+ ...(options?.additionalInstrumentations ?? []),
70
+ ],
71
+ });
72
+ }
73
+ instantiateSdk(config) {
74
+ return new sdk_node_1.NodeSDK(config);
75
+ }
76
+ start() {
77
+ this.sdk.start();
78
+ }
79
+ shutdown() {
80
+ return this.sdk.shutdown();
81
+ }
82
+ }
83
+ exports.KittlNodeTelemetry = KittlNodeTelemetry;
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@kittl/observability-node",
3
+ "version": "0.1.0",
4
+ "files": [
5
+ "dist"
6
+ ],
7
+ "main": "./dist/index.js",
8
+ "scripts": {
9
+ "build": "tsc -p tsconfig.build.json",
10
+ "lint:biome": "biome check ./src --error-on-warnings --no-errors-on-unmatched",
11
+ "lint": "pnpm run lint:biome",
12
+ "lint:fix": "pnpm run lint:biome --write",
13
+ "test": "tsx --test",
14
+ "verify:types": "tsc --noEmit"
15
+ },
16
+ "devDependencies": {
17
+ "@repo/biome-config": "workspace:*",
18
+ "@repo/typescript-config": "workspace:*",
19
+ "tsx": "4.19.2"
20
+ },
21
+ "lint-staged": {
22
+ "**/*.{ts,tsx,js,jsx,json}": [
23
+ "biome check --write --error-on-warnings --no-errors-on-unmatched"
24
+ ]
25
+ },
26
+ "dependencies": {
27
+ "@opentelemetry/api": "^1.9.0",
28
+ "@opentelemetry/auto-instrumentations-node": "0.75.0",
29
+ "@opentelemetry/exporter-prometheus": "0.217.0",
30
+ "@opentelemetry/exporter-trace-otlp-http": "^0.202.0",
31
+ "@opentelemetry/instrumentation": "0.217.0",
32
+ "@opentelemetry/instrumentation-express": "^0.51.0",
33
+ "@opentelemetry/resources": "^2.0.1",
34
+ "@opentelemetry/sdk-node": "0.217.0",
35
+ "@opentelemetry/semantic-conventions": "^1.30.0",
36
+ "@repo/logger": "workspace:*"
37
+ }
38
+ }