@ogcio/o11y-sdk-node 0.1.0-beta.1 → 0.1.0-beta.10
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/CHANGELOG.md +74 -0
- package/README.md +222 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -0
- package/dist/lib/exporter/console.d.ts +3 -0
- package/dist/lib/exporter/console.js +20 -0
- package/dist/lib/exporter/grpc.d.ts +3 -0
- package/dist/lib/{grpc.js → exporter/grpc.js} +15 -9
- package/dist/lib/exporter/http.d.ts +3 -0
- package/dist/lib/{http.js → exporter/http.js} +15 -9
- package/dist/lib/exporter/index.d.ts +8 -0
- package/dist/lib/index.d.ts +29 -6
- package/dist/lib/instrumentation.node.js +32 -5
- package/dist/lib/metrics.d.ts +18 -0
- package/dist/lib/metrics.js +24 -0
- package/dist/lib/processor/enrich-logger-processor.d.ts +10 -0
- package/dist/lib/processor/enrich-logger-processor.js +19 -0
- package/dist/lib/processor/enrich-span-processor.d.ts +11 -0
- package/dist/lib/processor/enrich-span-processor.js +22 -0
- package/dist/lib/resource.d.ts +7 -0
- package/dist/lib/resource.js +18 -0
- package/dist/lib/traces.d.ts +1 -0
- package/dist/lib/traces.js +4 -0
- package/dist/lib/url-sampler.d.ts +10 -0
- package/dist/lib/url-sampler.js +25 -0
- package/dist/lib/utils.d.ts +4 -2
- package/dist/lib/utils.js +8 -3
- package/dist/package.json +57 -0
- package/dist/vitest.config.js +15 -1
- package/index.ts +4 -2
- package/lib/exporter/console.ts +31 -0
- package/lib/{grpc.ts → exporter/grpc.ts} +19 -11
- package/lib/{http.ts → exporter/http.ts} +19 -11
- package/lib/exporter/index.ts +9 -0
- package/lib/index.ts +37 -5
- package/lib/instrumentation.node.ts +42 -7
- package/lib/metrics.ts +75 -0
- package/lib/processor/enrich-logger-processor.ts +34 -0
- package/lib/processor/enrich-span-processor.ts +39 -0
- package/lib/resource.ts +30 -0
- package/lib/traces.ts +5 -0
- package/lib/url-sampler.ts +52 -0
- package/lib/utils.ts +16 -4
- package/package.json +32 -25
- package/test/index.test.ts +9 -0
- package/test/integration/README.md +26 -0
- package/test/integration/integration.test.ts +58 -0
- package/test/integration/run.sh +88 -0
- package/test/metrics.test.ts +142 -0
- package/test/node-config.test.ts +70 -31
- package/test/processor/enrich-logger-processor.test.ts +58 -0
- package/test/processor/enrich-span-processor.test.ts +104 -0
- package/test/resource.test.ts +33 -0
- package/test/url-sampler.test.ts +215 -0
- package/test/utils/alloy-log-parser.ts +46 -0
- package/test/validation.test.ts +31 -0
- package/tsconfig.json +2 -1
- package/vitest.config.ts +15 -1
- package/coverage/cobertura-coverage.xml +0 -199
- package/coverage/lcov-report/base.css +0 -224
- package/coverage/lcov-report/block-navigation.js +0 -87
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +0 -131
- package/coverage/lcov-report/prettify.css +0 -1
- package/coverage/lcov-report/prettify.js +0 -2
- package/coverage/lcov-report/sdk-node/index.html +0 -116
- package/coverage/lcov-report/sdk-node/index.ts.html +0 -106
- package/coverage/lcov-report/sdk-node/lib/grpc.ts.html +0 -178
- package/coverage/lcov-report/sdk-node/lib/http.ts.html +0 -190
- package/coverage/lcov-report/sdk-node/lib/index.html +0 -191
- package/coverage/lcov-report/sdk-node/lib/index.ts.html +0 -265
- package/coverage/lcov-report/sdk-node/lib/instrumentation.node.ts.html +0 -310
- package/coverage/lcov-report/sdk-node/lib/options.ts.html +0 -109
- package/coverage/lcov-report/sdk-node/lib/utils.ts.html +0 -115
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +0 -196
- package/coverage/lcov.info +0 -206
- package/dist/lib/grpc.d.ts +0 -3
- package/dist/lib/http.d.ts +0 -3
- package/dist/lib/options.d.ts +0 -7
- package/lib/options.ts +0 -8
- package/test-report.xml +0 -39
- /package/dist/lib/{options.js → exporter/index.js} +0 -0
package/lib/resource.ts
ADDED
|
@@ -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,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 {
|
|
6
|
+
import { tracing } from "@opentelemetry/sdk-node";
|
|
3
7
|
|
|
4
8
|
export const LogRecordProcessorMap: Record<
|
|
5
9
|
SDKCollectorMode,
|
|
6
|
-
typeof
|
|
10
|
+
typeof SimpleLogRecordProcessor | typeof BatchLogRecordProcessor
|
|
7
11
|
> = {
|
|
8
|
-
single:
|
|
9
|
-
batch:
|
|
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.
|
|
3
|
+
"version": "0.1.0-beta.10",
|
|
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"
|
|
@@ -24,26 +20,37 @@
|
|
|
24
20
|
"license": "ISC",
|
|
25
21
|
"dependencies": {
|
|
26
22
|
"@opentelemetry/api": "^1.9.0",
|
|
27
|
-
"@opentelemetry/auto-instrumentations-node": "^0.
|
|
28
|
-
"@opentelemetry/core": "
|
|
29
|
-
"@opentelemetry/exporter-logs-otlp-grpc": "^0.
|
|
30
|
-
"@opentelemetry/exporter-logs-otlp-http": "^0.
|
|
31
|
-
"@opentelemetry/exporter-metrics-otlp-grpc": "^0.
|
|
32
|
-
"@opentelemetry/exporter-metrics-otlp-http": "^0.
|
|
33
|
-
"@opentelemetry/exporter-trace-otlp-grpc": "^0.
|
|
34
|
-
"@opentelemetry/exporter-trace-otlp-http": "^0.
|
|
35
|
-
"@opentelemetry/instrumentation": "^0.
|
|
36
|
-
"@opentelemetry/otlp-exporter-base": "^0.
|
|
37
|
-
"@opentelemetry/
|
|
38
|
-
"@opentelemetry/sdk-
|
|
39
|
-
"
|
|
23
|
+
"@opentelemetry/auto-instrumentations-node": "^0.57.1",
|
|
24
|
+
"@opentelemetry/core": "^2.0.0",
|
|
25
|
+
"@opentelemetry/exporter-logs-otlp-grpc": "^0.200.0",
|
|
26
|
+
"@opentelemetry/exporter-logs-otlp-http": "^0.200.0",
|
|
27
|
+
"@opentelemetry/exporter-metrics-otlp-grpc": "^0.200.0",
|
|
28
|
+
"@opentelemetry/exporter-metrics-otlp-http": "^0.200.0",
|
|
29
|
+
"@opentelemetry/exporter-trace-otlp-grpc": "^0.200.0",
|
|
30
|
+
"@opentelemetry/exporter-trace-otlp-http": "^0.200.0",
|
|
31
|
+
"@opentelemetry/instrumentation": "^0.200.0",
|
|
32
|
+
"@opentelemetry/otlp-exporter-base": "^0.200.0",
|
|
33
|
+
"@opentelemetry/resources": "^2.0.0",
|
|
34
|
+
"@opentelemetry/sdk-logs": "^0.200.0",
|
|
35
|
+
"@opentelemetry/sdk-metrics": "^2.0.0",
|
|
36
|
+
"@opentelemetry/sdk-node": "^0.200.0",
|
|
37
|
+
"@opentelemetry/sdk-trace-base": "^2.0.0"
|
|
40
38
|
},
|
|
41
39
|
"devDependencies": {
|
|
42
|
-
"@types/
|
|
43
|
-
"@
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
|
|
40
|
+
"@types/node": "^22.14.0",
|
|
41
|
+
"@vitest/coverage-v8": "^3.1.1",
|
|
42
|
+
"tsx": "^4.19.3",
|
|
43
|
+
"typescript": "^5.8.3",
|
|
44
|
+
"vitest": "^3.1.1"
|
|
45
|
+
},
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=20.6.0"
|
|
48
|
+
},
|
|
49
|
+
"scripts": {
|
|
50
|
+
"build": "rm -rf dist && tsc -p tsconfig.json",
|
|
51
|
+
"test": "vitest",
|
|
52
|
+
"test:unit": "vitest --project unit",
|
|
53
|
+
"test:integration": "pnpm --filter @ogcio/o11y run prepare:integration && vitest --project integration",
|
|
54
|
+
"test:integration:dryrun": "vitest --project integration"
|
|
48
55
|
}
|
|
49
|
-
}
|
|
56
|
+
}
|
package/test/index.test.ts
CHANGED
|
@@ -12,6 +12,15 @@ describe("instrumentNode", () => {
|
|
|
12
12
|
serviceName: "custom-service",
|
|
13
13
|
collectorUrl: "http://custom-collector.com",
|
|
14
14
|
protocol: "grpc",
|
|
15
|
+
resourceAttributes: {
|
|
16
|
+
"team.infra.cluster": "dev-01",
|
|
17
|
+
"team.infra.pod": "01",
|
|
18
|
+
"team.service.type": "fastify",
|
|
19
|
+
},
|
|
20
|
+
spanAttributes: {
|
|
21
|
+
"signal.namespace": "example",
|
|
22
|
+
"signal.number": () => "callback",
|
|
23
|
+
},
|
|
15
24
|
};
|
|
16
25
|
|
|
17
26
|
const buildNodeInstrumentation = await import(
|
|
@@ -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
|
+
- persit 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
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { describe, test, expect, vi, beforeEach, assert } from "vitest";
|
|
2
|
+
import { getMetric, MetricsParams } from "../lib/metrics";
|
|
3
|
+
|
|
4
|
+
const mockMeter = {
|
|
5
|
+
createGauge: vi.fn(),
|
|
6
|
+
createHistogram: vi.fn(),
|
|
7
|
+
createCounter: vi.fn(),
|
|
8
|
+
createUpDownCounter: vi.fn(),
|
|
9
|
+
createObservableCounter: vi.fn(),
|
|
10
|
+
createObservableUpDownCounter: vi.fn(),
|
|
11
|
+
createObservableGauge: vi.fn(),
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
vi.mock("@opentelemetry/api", async () => {
|
|
15
|
+
const { createNoopMeter } = await import("@opentelemetry/api");
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
metrics: {
|
|
19
|
+
getMeter: vi.fn(() => mockMeter),
|
|
20
|
+
},
|
|
21
|
+
createNoopMeter: createNoopMeter,
|
|
22
|
+
};
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe("MetricsFactoryMap", () => {
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
vi.clearAllMocks();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const validMetricParams: MetricsParams = {
|
|
31
|
+
metricName: "test-metric",
|
|
32
|
+
meterName: "test-meter",
|
|
33
|
+
options: { description: "A test metric" },
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
test("should call createGauge when type is 'gauge'", () => {
|
|
37
|
+
mockMeter.createGauge.mockReturnValue("mocked-gauge");
|
|
38
|
+
|
|
39
|
+
const result = getMetric("gauge", validMetricParams);
|
|
40
|
+
|
|
41
|
+
expect(result).toBe("mocked-gauge");
|
|
42
|
+
expect(mockMeter.createGauge).toHaveBeenCalledWith(
|
|
43
|
+
validMetricParams.metricName,
|
|
44
|
+
validMetricParams.options,
|
|
45
|
+
);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("should call createHistogram when type is 'histogram'", () => {
|
|
49
|
+
mockMeter.createHistogram.mockReturnValue("mocked-histogram");
|
|
50
|
+
|
|
51
|
+
const result = getMetric("histogram", validMetricParams);
|
|
52
|
+
|
|
53
|
+
expect(result).toBe("mocked-histogram");
|
|
54
|
+
expect(mockMeter.createHistogram).toHaveBeenCalledWith(
|
|
55
|
+
validMetricParams.metricName,
|
|
56
|
+
validMetricParams.options,
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("should call createCounter when type is 'counter'", () => {
|
|
61
|
+
mockMeter.createCounter.mockReturnValue("mocked-counter");
|
|
62
|
+
|
|
63
|
+
const result = getMetric("counter", validMetricParams);
|
|
64
|
+
|
|
65
|
+
expect(result).toBe("mocked-counter");
|
|
66
|
+
expect(mockMeter.createCounter).toHaveBeenCalledWith(
|
|
67
|
+
validMetricParams.metricName,
|
|
68
|
+
validMetricParams.options,
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("should call createUpDownCounter when type is 'updowncounter'", () => {
|
|
73
|
+
mockMeter.createUpDownCounter.mockReturnValue("mocked-updowncounter");
|
|
74
|
+
|
|
75
|
+
const result = getMetric("updowncounter", validMetricParams);
|
|
76
|
+
|
|
77
|
+
expect(result).toBe("mocked-updowncounter");
|
|
78
|
+
expect(mockMeter.createUpDownCounter).toHaveBeenCalledWith(
|
|
79
|
+
validMetricParams.metricName,
|
|
80
|
+
validMetricParams.options,
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("should call createObservableCounter when type is 'async-counter'", () => {
|
|
85
|
+
mockMeter.createObservableCounter.mockReturnValue("mocked-async-counter");
|
|
86
|
+
|
|
87
|
+
const result = getMetric("async-counter", validMetricParams);
|
|
88
|
+
|
|
89
|
+
expect(result).toBe("mocked-async-counter");
|
|
90
|
+
expect(mockMeter.createObservableCounter).toHaveBeenCalledWith(
|
|
91
|
+
validMetricParams.metricName,
|
|
92
|
+
validMetricParams.options,
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("should call createObservableUpDownCounter when type is 'async-updowncounter'", () => {
|
|
97
|
+
mockMeter.createObservableUpDownCounter.mockReturnValue(
|
|
98
|
+
"mocked-async-updowncounter",
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
const result = getMetric("async-updowncounter", validMetricParams);
|
|
102
|
+
|
|
103
|
+
expect(result).toBe("mocked-async-updowncounter");
|
|
104
|
+
expect(mockMeter.createObservableUpDownCounter).toHaveBeenCalledWith(
|
|
105
|
+
validMetricParams.metricName,
|
|
106
|
+
validMetricParams.options,
|
|
107
|
+
);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("should call createObservableGauge when type is 'async-gauge'", () => {
|
|
111
|
+
mockMeter.createObservableGauge.mockReturnValue("mocked-async-gauge");
|
|
112
|
+
|
|
113
|
+
const result = getMetric("async-gauge", validMetricParams);
|
|
114
|
+
|
|
115
|
+
expect(result).toBe("mocked-async-gauge");
|
|
116
|
+
expect(mockMeter.createObservableGauge).toHaveBeenCalledWith(
|
|
117
|
+
validMetricParams.metricName,
|
|
118
|
+
validMetricParams.options,
|
|
119
|
+
);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("should throw an error for unsupported metric types", () => {
|
|
123
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
124
|
+
const invalidMetricType = "invalid-type" as any;
|
|
125
|
+
|
|
126
|
+
expect(() => getMetric(invalidMetricType, validMetricParams)).toThrow(
|
|
127
|
+
`Unsupported metric type: ${invalidMetricType}`,
|
|
128
|
+
);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("should return noop metric fallback for null config", async () => {
|
|
132
|
+
const nullMetricParams: MetricsParams = {
|
|
133
|
+
metricName: null!,
|
|
134
|
+
meterName: "",
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const result = getMetric("async-gauge", nullMetricParams);
|
|
138
|
+
|
|
139
|
+
assert.isNotNull(result);
|
|
140
|
+
assert.equal(result.constructor.name, "NoopObservableGaugeMetric");
|
|
141
|
+
});
|
|
142
|
+
});
|
package/test/node-config.test.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { test, describe, assert, expect } from "vitest";
|
|
2
2
|
import buildNodeInstrumentation from "../lib/instrumentation.node.js";
|
|
3
|
-
import { NodeSDK, logs } from "@opentelemetry/sdk-node";
|
|
3
|
+
import { NodeSDK, logs, metrics, tracing } from "@opentelemetry/sdk-node";
|
|
4
4
|
import { OTLPTraceExporter as GRPC_OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-grpc";
|
|
5
5
|
import { OTLPMetricExporter as GRPC_OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-grpc";
|
|
6
6
|
import { OTLPLogExporter as GRPC_OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-grpc";
|
|
@@ -9,7 +9,17 @@ import { OTLPTraceExporter as HTTP_OTLPTraceExporter } from "@opentelemetry/expo
|
|
|
9
9
|
import { OTLPMetricExporter as HTTP_OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
|
|
10
10
|
import { OTLPLogExporter as HTTP_OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
|
|
11
11
|
import { NodeSDKConfig } from "../lib/index.js";
|
|
12
|
-
import buildHttpExporters from "../lib/http.js";
|
|
12
|
+
import buildHttpExporters from "../lib/exporter/http.js";
|
|
13
|
+
import {
|
|
14
|
+
BatchSpanProcessor,
|
|
15
|
+
ConsoleSpanExporter,
|
|
16
|
+
SimpleSpanProcessor,
|
|
17
|
+
} from "@opentelemetry/sdk-trace-base";
|
|
18
|
+
import {
|
|
19
|
+
BatchLogRecordProcessor,
|
|
20
|
+
SimpleLogRecordProcessor,
|
|
21
|
+
} from "@opentelemetry/sdk-logs";
|
|
22
|
+
import { EnrichSpanProcessor } from "../lib/processor/enrich-span-processor.js";
|
|
13
23
|
|
|
14
24
|
describe("verify config settings", () => {
|
|
15
25
|
const commonConfig = {
|
|
@@ -21,6 +31,7 @@ describe("verify config settings", () => {
|
|
|
21
31
|
const config: NodeSDKConfig = {
|
|
22
32
|
...commonConfig,
|
|
23
33
|
protocol: "grpc",
|
|
34
|
+
collectorMode: "batch",
|
|
24
35
|
diagLogLevel: "NONE",
|
|
25
36
|
};
|
|
26
37
|
|
|
@@ -31,23 +42,21 @@ describe("verify config settings", () => {
|
|
|
31
42
|
const _configuration = sdk["_configuration"];
|
|
32
43
|
assert.equal(_configuration.serviceName, commonConfig.serviceName);
|
|
33
44
|
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
45
|
+
const logs = _configuration.logRecordProcessors;
|
|
46
|
+
|
|
47
|
+
assert.equal(logs.length, 2);
|
|
48
|
+
assert.ok(logs[1] instanceof BatchLogRecordProcessor);
|
|
49
|
+
|
|
50
|
+
const spans = _configuration.spanProcessors;
|
|
51
|
+
|
|
52
|
+
assert.equal(spans.length, 2);
|
|
53
|
+
assert.ok(spans[0] instanceof BatchSpanProcessor);
|
|
54
|
+
assert.ok(spans[1] instanceof EnrichSpanProcessor);
|
|
40
55
|
|
|
41
|
-
const logRecordProcessors = _configuration.logRecordProcessors;
|
|
42
|
-
assert.equal(logRecordProcessors.length, 1);
|
|
43
|
-
// assert default signals sending mode
|
|
44
|
-
assert.ok(logRecordProcessors[0] instanceof logs.BatchLogRecordProcessor);
|
|
45
56
|
assert.ok(
|
|
46
|
-
|
|
57
|
+
_configuration.metricReader instanceof
|
|
58
|
+
metrics.PeriodicExportingMetricReader,
|
|
47
59
|
);
|
|
48
|
-
|
|
49
|
-
const metricReader = _configuration.metricReader;
|
|
50
|
-
assert.ok(metricReader._exporter instanceof GRPC_OTLPMetricExporter);
|
|
51
60
|
});
|
|
52
61
|
|
|
53
62
|
test("http config", () => {
|
|
@@ -63,23 +72,53 @@ describe("verify config settings", () => {
|
|
|
63
72
|
const _configuration = sdk["_configuration"];
|
|
64
73
|
assert.equal(_configuration.serviceName, commonConfig.serviceName);
|
|
65
74
|
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
75
|
+
const logs = _configuration.logRecordProcessors;
|
|
76
|
+
|
|
77
|
+
assert.equal(logs.length, 2);
|
|
78
|
+
assert.ok(logs[1] instanceof BatchLogRecordProcessor);
|
|
79
|
+
|
|
80
|
+
const spans = _configuration.spanProcessors;
|
|
81
|
+
|
|
82
|
+
assert.equal(spans.length, 2);
|
|
83
|
+
assert.ok(spans[0] instanceof BatchSpanProcessor);
|
|
84
|
+
assert.ok(spans[1] instanceof EnrichSpanProcessor);
|
|
72
85
|
|
|
73
|
-
const logRecordProcessors = _configuration.logRecordProcessors;
|
|
74
|
-
assert.equal(logRecordProcessors.length, 1);
|
|
75
|
-
// assert default signals sending mode
|
|
76
|
-
assert.ok(logRecordProcessors[0] instanceof logs.BatchLogRecordProcessor);
|
|
77
86
|
assert.ok(
|
|
78
|
-
|
|
87
|
+
_configuration.metricReader instanceof
|
|
88
|
+
metrics.PeriodicExportingMetricReader,
|
|
79
89
|
);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("console - console config", () => {
|
|
93
|
+
const config: NodeSDKConfig = {
|
|
94
|
+
...commonConfig,
|
|
95
|
+
protocol: "console",
|
|
96
|
+
diagLogLevel: "NONE",
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const sdk: NodeSDK = buildNodeInstrumentation(config)!;
|
|
100
|
+
assert.ok(sdk);
|
|
101
|
+
|
|
102
|
+
const _configuration = sdk["_configuration"];
|
|
103
|
+
assert.equal(_configuration.serviceName, commonConfig.serviceName);
|
|
104
|
+
|
|
105
|
+
const logs = _configuration.logRecordProcessors;
|
|
106
|
+
|
|
107
|
+
// verify simple log processor for instant console logging
|
|
108
|
+
assert.equal(logs.length, 2);
|
|
109
|
+
assert.ok(logs[1] instanceof SimpleLogRecordProcessor);
|
|
110
|
+
|
|
111
|
+
const spans = _configuration.spanProcessors;
|
|
80
112
|
|
|
81
|
-
|
|
82
|
-
|
|
113
|
+
assert.equal(spans.length, 2);
|
|
114
|
+
// verify simple span for instant console logging
|
|
115
|
+
assert.ok(spans[0] instanceof SimpleSpanProcessor);
|
|
116
|
+
assert.ok(spans[1] instanceof EnrichSpanProcessor);
|
|
117
|
+
|
|
118
|
+
assert.ok(
|
|
119
|
+
_configuration.metricReader instanceof
|
|
120
|
+
metrics.PeriodicExportingMetricReader,
|
|
121
|
+
);
|
|
83
122
|
});
|
|
84
123
|
|
|
85
124
|
test("single log sending config", () => {
|
|
@@ -97,8 +136,8 @@ describe("verify config settings", () => {
|
|
|
97
136
|
const _configuration = sdk["_configuration"];
|
|
98
137
|
|
|
99
138
|
const logRecordProcessors = _configuration.logRecordProcessors;
|
|
100
|
-
assert.equal(logRecordProcessors.length,
|
|
101
|
-
assert.ok(logRecordProcessors[
|
|
139
|
+
assert.equal(logRecordProcessors.length, 2);
|
|
140
|
+
assert.ok(logRecordProcessors[1] instanceof SimpleLogRecordProcessor);
|
|
102
141
|
});
|
|
103
142
|
|
|
104
143
|
test("check if clear base endpoint final slash", () => {
|