@ogcio/o11y-sdk-node 0.1.0-beta.1 → 0.1.0-beta.11

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.
Files changed (86) hide show
  1. package/CHANGELOG.md +81 -0
  2. package/README.md +222 -0
  3. package/dist/index.d.ts +3 -2
  4. package/dist/index.js +2 -0
  5. package/dist/lib/exporter/console.d.ts +3 -0
  6. package/dist/lib/exporter/console.js +20 -0
  7. package/dist/lib/exporter/grpc.d.ts +3 -0
  8. package/dist/lib/exporter/grpc.js +41 -0
  9. package/dist/lib/exporter/http.d.ts +3 -0
  10. package/dist/lib/{http.js → exporter/http.js} +15 -9
  11. package/dist/lib/exporter/index.d.ts +8 -0
  12. package/dist/lib/index.d.ts +36 -6
  13. package/dist/lib/instrumentation.node.d.ts +1 -1
  14. package/dist/lib/instrumentation.node.js +47 -23
  15. package/dist/lib/metrics.d.ts +18 -0
  16. package/dist/lib/metrics.js +24 -0
  17. package/dist/lib/processor/enrich-logger-processor.d.ts +10 -0
  18. package/dist/lib/processor/enrich-logger-processor.js +19 -0
  19. package/dist/lib/processor/enrich-span-processor.d.ts +11 -0
  20. package/dist/lib/processor/enrich-span-processor.js +22 -0
  21. package/dist/lib/resource.d.ts +7 -0
  22. package/dist/lib/resource.js +18 -0
  23. package/dist/lib/traces.d.ts +1 -0
  24. package/dist/lib/traces.js +4 -0
  25. package/dist/lib/url-sampler.d.ts +10 -0
  26. package/dist/lib/url-sampler.js +25 -0
  27. package/dist/lib/utils.d.ts +4 -2
  28. package/dist/lib/utils.js +8 -3
  29. package/dist/package.json +58 -0
  30. package/dist/vitest.config.js +15 -1
  31. package/index.ts +4 -2
  32. package/lib/exporter/console.ts +31 -0
  33. package/lib/exporter/grpc.ts +52 -0
  34. package/lib/{http.ts → exporter/http.ts} +19 -11
  35. package/lib/exporter/index.ts +9 -0
  36. package/lib/index.ts +46 -5
  37. package/lib/instrumentation.node.ts +61 -28
  38. package/lib/metrics.ts +75 -0
  39. package/lib/processor/enrich-logger-processor.ts +34 -0
  40. package/lib/processor/enrich-span-processor.ts +39 -0
  41. package/lib/resource.ts +30 -0
  42. package/lib/traces.ts +5 -0
  43. package/lib/url-sampler.ts +52 -0
  44. package/lib/utils.ts +16 -4
  45. package/package.json +33 -25
  46. package/test/index.test.ts +22 -12
  47. package/test/integration/README.md +26 -0
  48. package/test/integration/integration.test.ts +58 -0
  49. package/test/integration/run.sh +88 -0
  50. package/test/metrics.test.ts +142 -0
  51. package/test/node-config.test.ts +76 -44
  52. package/test/processor/enrich-logger-processor.test.ts +58 -0
  53. package/test/processor/enrich-span-processor.test.ts +104 -0
  54. package/test/resource.test.ts +33 -0
  55. package/test/url-sampler.test.ts +215 -0
  56. package/test/utils/alloy-log-parser.ts +46 -0
  57. package/test/validation.test.ts +48 -11
  58. package/tsconfig.json +2 -1
  59. package/vitest.config.ts +15 -1
  60. package/coverage/cobertura-coverage.xml +0 -199
  61. package/coverage/lcov-report/base.css +0 -224
  62. package/coverage/lcov-report/block-navigation.js +0 -87
  63. package/coverage/lcov-report/favicon.png +0 -0
  64. package/coverage/lcov-report/index.html +0 -131
  65. package/coverage/lcov-report/prettify.css +0 -1
  66. package/coverage/lcov-report/prettify.js +0 -2
  67. package/coverage/lcov-report/sdk-node/index.html +0 -116
  68. package/coverage/lcov-report/sdk-node/index.ts.html +0 -106
  69. package/coverage/lcov-report/sdk-node/lib/grpc.ts.html +0 -178
  70. package/coverage/lcov-report/sdk-node/lib/http.ts.html +0 -190
  71. package/coverage/lcov-report/sdk-node/lib/index.html +0 -191
  72. package/coverage/lcov-report/sdk-node/lib/index.ts.html +0 -265
  73. package/coverage/lcov-report/sdk-node/lib/instrumentation.node.ts.html +0 -310
  74. package/coverage/lcov-report/sdk-node/lib/options.ts.html +0 -109
  75. package/coverage/lcov-report/sdk-node/lib/utils.ts.html +0 -115
  76. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  77. package/coverage/lcov-report/sorter.js +0 -196
  78. package/coverage/lcov.info +0 -206
  79. package/dist/lib/grpc.d.ts +0 -3
  80. package/dist/lib/grpc.js +0 -26
  81. package/dist/lib/http.d.ts +0 -3
  82. package/dist/lib/options.d.ts +0 -7
  83. package/lib/grpc.ts +0 -31
  84. package/lib/options.ts +0 -8
  85. package/test-report.xml +0 -39
  86. /package/dist/lib/{options.js → exporter/index.js} +0 -0
package/lib/metrics.ts ADDED
@@ -0,0 +1,75 @@
1
+ import {
2
+ Counter,
3
+ createNoopMeter,
4
+ Gauge,
5
+ Histogram,
6
+ Meter,
7
+ MetricOptions,
8
+ metrics,
9
+ ObservableCounter,
10
+ ObservableGauge,
11
+ ObservableUpDownCounter,
12
+ UpDownCounter,
13
+ Attributes,
14
+ } from "@opentelemetry/api";
15
+
16
+ type MetricTypeMap<TAttributes extends Attributes> = {
17
+ counter: Counter<TAttributes>;
18
+ histogram: Histogram<TAttributes>;
19
+ gauge: Gauge<TAttributes>;
20
+ updowncounter: UpDownCounter<TAttributes>;
21
+ "async-counter": ObservableCounter<TAttributes>;
22
+ "async-updowncounter": ObservableUpDownCounter<TAttributes>;
23
+ "async-gauge": ObservableGauge<TAttributes>;
24
+ };
25
+
26
+ export type MetricType = keyof MetricTypeMap<Attributes>;
27
+
28
+ const MetricsFactoryMap: Record<
29
+ MetricType,
30
+ (
31
+ meter: Meter,
32
+ ) => (
33
+ name: string,
34
+ options?: MetricOptions,
35
+ ) => MetricTypeMap<Attributes>[MetricType]
36
+ > = {
37
+ gauge: (meter: Meter) => meter.createGauge,
38
+ histogram: (meter: Meter) => meter.createHistogram,
39
+ counter: (meter: Meter) => meter.createCounter,
40
+ updowncounter: (meter: Meter) => meter.createUpDownCounter,
41
+ "async-counter": (meter: Meter) => meter.createObservableCounter,
42
+ "async-updowncounter": (meter: Meter) => meter.createObservableUpDownCounter,
43
+ "async-gauge": (meter: Meter) => meter.createObservableGauge,
44
+ } as const;
45
+
46
+ export interface MetricsParams {
47
+ meterName: string;
48
+ metricName: string;
49
+ options?: MetricOptions;
50
+ }
51
+
52
+ function getMeter({ meterName }: MetricsParams) {
53
+ if (!meterName) {
54
+ console.error("Invalid metric name!");
55
+ return createNoopMeter();
56
+ }
57
+
58
+ return metrics.getMeter(`custom_metric.${meterName}`);
59
+ }
60
+
61
+ export function getMetric<
62
+ T extends MetricType,
63
+ TAttributes extends Attributes = Attributes,
64
+ >(type: T, p: MetricsParams): MetricTypeMap<TAttributes>[T] {
65
+ const meter = getMeter(p);
66
+
67
+ if (!MetricsFactoryMap[type]) {
68
+ throw new Error(`Unsupported metric type: ${type}`);
69
+ }
70
+
71
+ return MetricsFactoryMap[type](meter).bind(meter)(
72
+ p.metricName,
73
+ p.options,
74
+ ) as MetricTypeMap<TAttributes>[T];
75
+ }
@@ -0,0 +1,34 @@
1
+ import { LogRecord, LogRecordProcessor } from "@opentelemetry/sdk-logs";
2
+ import { Context } from "@opentelemetry/api";
3
+ import { SignalAttributeValue } from "../index.js";
4
+
5
+ export class EnrichLogProcessor implements LogRecordProcessor {
6
+ private _spanAttributes?:
7
+ | Record<string, SignalAttributeValue | (() => SignalAttributeValue)>
8
+ | undefined = undefined;
9
+
10
+ constructor(
11
+ spanAttributes?: Record<
12
+ string,
13
+ SignalAttributeValue | (() => SignalAttributeValue)
14
+ >,
15
+ ) {
16
+ this._spanAttributes = spanAttributes;
17
+ }
18
+ forceFlush(): Promise<void> {
19
+ return Promise.resolve();
20
+ }
21
+ onEmit(logRecord: LogRecord, _context?: Context): void {
22
+ if (this._spanAttributes) {
23
+ for (const [key, value] of Object.entries(this._spanAttributes)) {
24
+ logRecord.setAttribute(
25
+ key,
26
+ typeof value === "function" ? value() : value,
27
+ );
28
+ }
29
+ }
30
+ }
31
+ shutdown(): Promise<void> {
32
+ return Promise.resolve();
33
+ }
34
+ }
@@ -0,0 +1,39 @@
1
+ import { Context } from "@opentelemetry/api";
2
+ import {
3
+ ReadableSpan,
4
+ Span,
5
+ SpanProcessor,
6
+ } from "@opentelemetry/sdk-trace-base";
7
+ import { SignalAttributeValue } from "../index.js";
8
+
9
+ export class EnrichSpanProcessor implements SpanProcessor {
10
+ private _spanAttributes:
11
+ | Record<string, SignalAttributeValue | (() => SignalAttributeValue)>
12
+ | undefined;
13
+
14
+ constructor(
15
+ spanAttributes?: Record<
16
+ string,
17
+ SignalAttributeValue | (() => SignalAttributeValue)
18
+ >,
19
+ ) {
20
+ this._spanAttributes = spanAttributes;
21
+ }
22
+
23
+ forceFlush(): Promise<void> {
24
+ return Promise.resolve();
25
+ }
26
+ onStart(span: Span, _context: Context): void {
27
+ if (this._spanAttributes != undefined) {
28
+ for (const [key, value] of Object.entries(this._spanAttributes)) {
29
+ span.setAttribute(key, typeof value === "function" ? value() : value);
30
+ }
31
+ }
32
+ }
33
+ onEnd(_span: ReadableSpan): void {
34
+ return;
35
+ }
36
+ shutdown(): Promise<void> {
37
+ return Promise.resolve();
38
+ }
39
+ }
@@ -0,0 +1,30 @@
1
+ import {
2
+ ResourceDetector,
3
+ DetectedResource,
4
+ DetectedResourceAttributes,
5
+ } from "@opentelemetry/resources";
6
+ import { SignalAttributeValue } from "./index.js";
7
+ import packageJson from "../package.json" with { type: "json" };
8
+
9
+ export class ObservabilityResourceDetector implements ResourceDetector {
10
+ private _resourceAttributes: Record<string, SignalAttributeValue> | undefined;
11
+
12
+ constructor(resourceAttributes?: Record<string, SignalAttributeValue>) {
13
+ this._resourceAttributes = resourceAttributes;
14
+ }
15
+
16
+ detect(): DetectedResource {
17
+ let attributes: DetectedResourceAttributes = {};
18
+
19
+ if (this._resourceAttributes) {
20
+ attributes = {
21
+ ...this._resourceAttributes,
22
+ };
23
+ }
24
+
25
+ attributes["o11y.sdk.name"] = packageJson.name;
26
+ attributes["o11y.sdk.version"] = packageJson.version;
27
+
28
+ return { attributes };
29
+ }
30
+ }
package/lib/traces.ts ADDED
@@ -0,0 +1,5 @@
1
+ import { trace } from "@opentelemetry/api";
2
+
3
+ export function getActiveSpan() {
4
+ return trace.getActiveSpan();
5
+ }
@@ -0,0 +1,52 @@
1
+ import { Attributes, Context, Link, SpanKind } from "@opentelemetry/api";
2
+ import {
3
+ Sampler,
4
+ SamplingDecision,
5
+ SamplingResult,
6
+ } from "@opentelemetry/sdk-trace-base";
7
+ import { SamplerCondition } from "./index.js";
8
+
9
+ export class UrlSampler implements Sampler {
10
+ private _samplerCondition: SamplerCondition[];
11
+ private _nextSampler: Sampler;
12
+
13
+ constructor(samplerCondition: SamplerCondition[] = [], nextSampler: Sampler) {
14
+ this._samplerCondition = samplerCondition;
15
+ this._nextSampler = nextSampler;
16
+ }
17
+
18
+ shouldSample(
19
+ _context: Context,
20
+ traceId: string,
21
+ _spanName: string,
22
+ _spanKind: SpanKind,
23
+ attributes: Attributes,
24
+ _links: Link[],
25
+ ): SamplingResult {
26
+ const url: string | undefined = attributes["http.target"]?.toString();
27
+
28
+ if (url) {
29
+ for (const condition of this._samplerCondition) {
30
+ if (
31
+ (condition.type === "equals" && url === condition.url) ||
32
+ (condition.type === "endsWith" && url.endsWith(condition.url)) ||
33
+ (condition.type === "includes" && url.includes(condition.url))
34
+ ) {
35
+ return { decision: SamplingDecision.NOT_RECORD };
36
+ }
37
+ }
38
+ }
39
+
40
+ return this._nextSampler.shouldSample(
41
+ _context,
42
+ traceId,
43
+ _spanName,
44
+ _spanKind,
45
+ attributes,
46
+ _links,
47
+ );
48
+ }
49
+ toString(): string {
50
+ return "UrlSampler";
51
+ }
52
+ }
package/lib/utils.ts CHANGED
@@ -1,10 +1,22 @@
1
+ import {
2
+ BatchLogRecordProcessor,
3
+ SimpleLogRecordProcessor,
4
+ } from "@opentelemetry/sdk-logs";
1
5
  import { SDKCollectorMode } from "./index.js";
2
- import { logs } from "@opentelemetry/sdk-node";
6
+ import { tracing } from "@opentelemetry/sdk-node";
3
7
 
4
8
  export const LogRecordProcessorMap: Record<
5
9
  SDKCollectorMode,
6
- typeof logs.SimpleLogRecordProcessor | typeof logs.BatchLogRecordProcessor
10
+ typeof SimpleLogRecordProcessor | typeof BatchLogRecordProcessor
7
11
  > = {
8
- single: logs.SimpleLogRecordProcessor,
9
- batch: logs.BatchLogRecordProcessor,
12
+ single: SimpleLogRecordProcessor,
13
+ batch: BatchLogRecordProcessor,
14
+ };
15
+
16
+ export const SpanProcessorMap: Record<
17
+ SDKCollectorMode,
18
+ typeof tracing.SimpleSpanProcessor | typeof tracing.BatchSpanProcessor
19
+ > = {
20
+ single: tracing.SimpleSpanProcessor,
21
+ batch: tracing.BatchSpanProcessor,
10
22
  };
package/package.json CHANGED
@@ -1,13 +1,9 @@
1
1
  {
2
2
  "name": "@ogcio/o11y-sdk-node",
3
- "version": "0.1.0-beta.1",
3
+ "version": "0.1.0-beta.11",
4
4
  "description": "Opentelemetry standard instrumentation SDK for NodeJS based project",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
7
- "scripts": {
8
- "build": "rm -rf dist && tsc -p tsconfig.json",
9
- "test": "vitest"
10
- },
11
7
  "exports": {
12
8
  ".": "./dist/index.js",
13
9
  "./*": "./dist/*.js"
@@ -23,27 +19,39 @@
23
19
  "author": "team:ogcio/observability",
24
20
  "license": "ISC",
25
21
  "dependencies": {
22
+ "@grpc/grpc-js": "^1.13.4",
26
23
  "@opentelemetry/api": "^1.9.0",
27
- "@opentelemetry/auto-instrumentations-node": "^0.53.0",
28
- "@opentelemetry/core": "1.28.0",
29
- "@opentelemetry/exporter-logs-otlp-grpc": "^0.55.0",
30
- "@opentelemetry/exporter-logs-otlp-http": "^0.55.0",
31
- "@opentelemetry/exporter-metrics-otlp-grpc": "^0.55.0",
32
- "@opentelemetry/exporter-metrics-otlp-http": "^0.55.0",
33
- "@opentelemetry/exporter-trace-otlp-grpc": "^0.55.0",
34
- "@opentelemetry/exporter-trace-otlp-http": "^0.55.0",
35
- "@opentelemetry/instrumentation": "^0.55.0",
36
- "@opentelemetry/otlp-exporter-base": "^0.55.0",
37
- "@opentelemetry/sdk-metrics": "^1.28.0",
38
- "@opentelemetry/sdk-node": "^0.55.0",
39
- "is-url": "^1.2.4"
24
+ "@opentelemetry/auto-instrumentations-node": "^0.60.0",
25
+ "@opentelemetry/core": "^2.0.1",
26
+ "@opentelemetry/exporter-logs-otlp-grpc": "^0.202.0",
27
+ "@opentelemetry/exporter-logs-otlp-http": "^0.202.0",
28
+ "@opentelemetry/exporter-metrics-otlp-grpc": "^0.202.0",
29
+ "@opentelemetry/exporter-metrics-otlp-http": "^0.202.0",
30
+ "@opentelemetry/exporter-trace-otlp-grpc": "^0.202.0",
31
+ "@opentelemetry/exporter-trace-otlp-http": "^0.202.0",
32
+ "@opentelemetry/instrumentation": "^0.202.0",
33
+ "@opentelemetry/otlp-exporter-base": "^0.202.0",
34
+ "@opentelemetry/resources": "^2.0.1",
35
+ "@opentelemetry/sdk-logs": "^0.202.0",
36
+ "@opentelemetry/sdk-metrics": "^2.0.1",
37
+ "@opentelemetry/sdk-node": "^0.202.0",
38
+ "@opentelemetry/sdk-trace-base": "^2.0.1"
40
39
  },
41
40
  "devDependencies": {
42
- "@types/is-url": "^1.2.32",
43
- "@types/node": "^22.10.0",
44
- "@vitest/coverage-v8": "^2.1.6",
45
- "tsx": "^4.19.2",
46
- "typescript": "^5.7.2",
47
- "vitest": "^2.1.6"
41
+ "@types/node": "^22.15.29",
42
+ "@vitest/coverage-v8": "^3.2.1",
43
+ "tsx": "^4.19.4",
44
+ "typescript": "^5.8.3",
45
+ "vitest": "^3.2.1"
46
+ },
47
+ "engines": {
48
+ "node": ">=20.6.0"
49
+ },
50
+ "scripts": {
51
+ "build": "rm -rf dist && tsc -p tsconfig.json",
52
+ "test": "vitest",
53
+ "test:unit": "vitest --project unit",
54
+ "test:integration": "pnpm --filter @ogcio/o11y run prepare:integration && vitest --project integration",
55
+ "test:integration:dryrun": "vitest --project integration"
48
56
  }
49
- }
57
+ }
@@ -1,29 +1,39 @@
1
- import { describe, test, expect, vi } from "vitest";
1
+ import { describe, test, expect, vi, beforeEach } from "vitest";
2
2
  import { NodeSDKConfig } from "../index";
3
3
  import { instrumentNode } from "../index";
4
-
5
- vi.mock("../lib/instrumentation.node", () => ({
6
- default: vi.fn(),
7
- }));
4
+ import * as buildNodeInstrumentationModule from "../lib/instrumentation.node";
8
5
 
9
6
  describe("instrumentNode", () => {
7
+ beforeEach(() => {
8
+ vi.restoreAllMocks();
9
+ });
10
+
10
11
  test("should call buildNodeInstrumentation with the provided config", async () => {
12
+ const instrumentationMock = vi
13
+ .spyOn(buildNodeInstrumentationModule, "default")
14
+ .mockImplementation(vi.fn());
15
+
11
16
  const config: NodeSDKConfig = {
12
17
  serviceName: "custom-service",
13
18
  collectorUrl: "http://custom-collector.com",
14
19
  protocol: "grpc",
20
+ resourceAttributes: {
21
+ "team.infra.cluster": "dev-01",
22
+ "team.infra.pod": "01",
23
+ "team.service.type": "fastify",
24
+ },
25
+ spanAttributes: {
26
+ "signal.namespace": "example",
27
+ "signal.number": () => "callback",
28
+ },
15
29
  };
16
30
 
17
- const buildNodeInstrumentation = await import(
18
- "../lib/instrumentation.node"
19
- );
20
-
21
- instrumentNode(config);
31
+ await instrumentNode(config);
22
32
 
23
- expect(buildNodeInstrumentation.default).toHaveBeenCalledWith(config);
33
+ expect(instrumentationMock).toHaveBeenCalledWith(config);
24
34
  });
25
35
 
26
36
  test("should not throw when called without arguments", () => {
27
- expect(() => instrumentNode()).not.toThrow();
37
+ expect(instrumentNode()).resolves.not.toThrow();
28
38
  });
29
39
  });
@@ -0,0 +1,26 @@
1
+ # Integration Test
2
+
3
+ This folder contains a setup for integration test with o11y node sdk.
4
+
5
+ ## Workflow
6
+
7
+ - Docker must be in running state
8
+ - Run the sh script `sh ./packages/sdk-node/test/integration/run.sh 1 .` from project root with following params
9
+ 1. pipeline build number, for local development, any number or string is fine
10
+ 2. root folder for docker context
11
+ - Change dir to `packages/sdk-node/`
12
+ - Run full test suite with `pnpm test`
13
+
14
+ ## Script
15
+
16
+ The `run.sh` script performs the following steps:
17
+
18
+ - build a docker image of a fastify app `/examples/fastify`
19
+ - setup an temporary test docker network
20
+ - run grafana alloy inside a docker container with a test configuration `/alloy/integration-test.alloy`
21
+ - ensure is running otherwise exit process
22
+ - run fastify app in a docker container
23
+ - ensure is running otherwise exit process
24
+ - execute some curl to the fastify microservice
25
+ - persist alloy log to a file and save to following path `/packages/sdk-node/test/integration/`
26
+ - docker turn down process (containers/network/image)
@@ -0,0 +1,58 @@
1
+ import { describe, test, assert } from "vitest";
2
+ import { parseLog } from "../utils/alloy-log-parser";
3
+ import { readFile } from "node:fs/promises";
4
+ import { join } from "node:path";
5
+
6
+ describe("instrumentation integration test", () => {
7
+ test("should exclude health url and process only dummy calls", async () => {
8
+ const data = await readFile(join(__dirname, "logs.txt"), "utf-8");
9
+
10
+ let health_traces_counter = 0;
11
+ let dummy_traces_counter = 0;
12
+
13
+ console.log(data.split(/\nts=/).length);
14
+
15
+ for (const line of data.split(/\nts=/)) {
16
+ const parsedLine: Record<string, object | string | number> =
17
+ parseLog(line);
18
+
19
+ if (
20
+ parsedLine["attributes"] &&
21
+ parsedLine["attributes"]["span_kind"] &&
22
+ parsedLine["attributes"]["span_kind"] === "trace"
23
+ ) {
24
+ if (parsedLine["attributes"]["http.target"] === "/api/dummy") {
25
+ dummy_traces_counter++;
26
+
27
+ // verify global sdk span resource
28
+ assert.equal(
29
+ parsedLine["resource_attributes"]["team.infra.pod"],
30
+ "01",
31
+ );
32
+ assert.equal(
33
+ parsedLine["resource_attributes"]["team.service.type"],
34
+ "fastify",
35
+ );
36
+
37
+ // verify global sdk span attributes
38
+ assert.equal(parsedLine["attributes"]["signal.namespace"], "example");
39
+
40
+ // verify runtime custom span inside dev application
41
+ assert.equal(parsedLine["attributes"]["business.info"], "dummy");
42
+ assert.equal(
43
+ parsedLine["attributes"]["business.request_type"],
44
+ "application/json",
45
+ );
46
+
47
+ continue;
48
+ }
49
+ if (parsedLine["attributes"]["http.target"] === "/api/health") {
50
+ health_traces_counter++;
51
+ }
52
+ }
53
+ }
54
+
55
+ assert.equal(health_traces_counter, 0);
56
+ assert.equal(dummy_traces_counter, 2);
57
+ });
58
+ });
@@ -0,0 +1,88 @@
1
+ BUILD_ID=$1
2
+ ROOT_PATH=$2
3
+
4
+ NETWORK_NAME="${BUILD_ID}_testnetwork"
5
+ ALLOY_CONTAINER_NAME="integrationalloy"
6
+ NODE_CONTAINER_NAME="${BUILD_ID}_fastify_app"
7
+ ERROR_CODE=0
8
+
9
+ docker build -t ${NODE_CONTAINER_NAME}:${BUILD_ID} -f $ROOT_PATH/examples/fastify/Dockerfile $ROOT_PATH/
10
+
11
+ docker network create $NETWORK_NAME
12
+
13
+ docker run -d \
14
+ -v "$ROOT_PATH/alloy/integration-test.alloy:/etc/alloy/config.alloy:ro" \
15
+ --network $NETWORK_NAME \
16
+ --name $ALLOY_CONTAINER_NAME \
17
+ -p 4317:4317 \
18
+ -p 4318:4318 \
19
+ grafana/alloy \
20
+ run --server.http.listen-addr=0.0.0.0:12345 --stability.level=experimental /etc/alloy/config.alloy
21
+
22
+ MAX_RETRIES=10
23
+ COUNTER=0
24
+ echo "$ALLOY_CONTAINER_NAME container status"
25
+ until [ "$(docker inspect -f {{.State.Running}} $ALLOY_CONTAINER_NAME)" = true ]; do
26
+ sleep 2
27
+ docker inspect -f {{.State.Running}} $ALLOY_CONTAINER_NAME
28
+ COUNTER=$((COUNTER + 1))
29
+ if [ $COUNTER -ge $MAX_RETRIES ]; then
30
+ echo "Exceeded maximum retries. Exiting."
31
+ ERROR_CODE=1
32
+ break
33
+ fi
34
+ done
35
+
36
+ if [[ $ERROR_CODE -eq 0 ]]; then
37
+ docker run --detach \
38
+ --network $NETWORK_NAME \
39
+ --name $NODE_CONTAINER_NAME \
40
+ -e DB_DISABLED="true" \
41
+ -e SERVER_HOST="0.0.0.0" \
42
+ -e OTEL_COLLECTOR_URL="http://integrationalloy:4317" \
43
+ --health-cmd="curl -f http://${NODE_CONTAINER_NAME}:9091/api/health > /dev/null || exit 1" \
44
+ --health-start-period=1s \
45
+ --health-retries=10 \
46
+ --health-interval=1s \
47
+ -p 9091:9091 \
48
+ ${NODE_CONTAINER_NAME}:${BUILD_ID}
49
+
50
+ COUNTER=0
51
+ echo "$NODE_CONTAINER_NAME container status"
52
+ until [ "$(docker inspect -f {{.State.Health.Status}} $NODE_CONTAINER_NAME)" = "healthy" ]; do
53
+ sleep 1
54
+ docker inspect -f {{.State.Health.Status}} $NODE_CONTAINER_NAME
55
+ COUNTER=$((COUNTER + 1))
56
+ if [ $COUNTER -ge $MAX_RETRIES ]; then
57
+ echo "Exceeded maximum retries. Exiting."
58
+ ERROR_CODE=1
59
+ break
60
+ fi
61
+ done
62
+ fi
63
+
64
+ if [[ $ERROR_CODE -eq 0 ]]; then
65
+ sleep 2
66
+ curl -X GET -f http://localhost:9091/api/dummy -H 'Content-Type: application/json'
67
+ sleep 2
68
+ curl -X GET -f http://localhost:9091/api/dummy -H 'Content-Type: application/json'
69
+ fi
70
+
71
+ # sleep N seconds to await instrumentation flow send and receiving signals
72
+ sleep 10
73
+
74
+ # Copy logs from container to file
75
+ docker container logs $ALLOY_CONTAINER_NAME >&$ROOT_PATH/packages/sdk-node/test/integration/logs.txt
76
+ echo "log file at $ROOT_PATH/packages/sdk-node/test/integration/logs.txt"
77
+
78
+ docker container stop $ALLOY_CONTAINER_NAME
79
+ docker container stop $NODE_CONTAINER_NAME
80
+
81
+ docker container rm -f $ALLOY_CONTAINER_NAME
82
+ docker container rm -f $NODE_CONTAINER_NAME
83
+
84
+ docker image rm ${NODE_CONTAINER_NAME}:${BUILD_ID}
85
+
86
+ docker network rm $NETWORK_NAME
87
+
88
+ exit $ERROR_CODE