@ogcio/o11y-sdk-node 0.1.0-beta.4 → 0.1.0-beta.6

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 (38) hide show
  1. package/CHANGELOG.md +15 -1
  2. package/dist/lib/index.d.ts +11 -0
  3. package/dist/lib/instrumentation.node.js +12 -1
  4. package/dist/lib/url-sampler.d.ts +9 -0
  5. package/dist/lib/url-sampler.js +28 -0
  6. package/dist/package.json +4 -5
  7. package/dist/vitest.config.js +1 -1
  8. package/lib/index.ts +12 -0
  9. package/lib/instrumentation.node.ts +12 -1
  10. package/lib/url-sampler.ts +53 -0
  11. package/package.json +3 -4
  12. package/test/integration/README.md +26 -0
  13. package/test/integration/integration.test.ts +37 -0
  14. package/test/integration/run.sh +85 -0
  15. package/test/utils/alloy-log-parser.ts +46 -0
  16. package/vitest.config.ts +1 -1
  17. package/coverage/cobertura-coverage.xml +0 -310
  18. package/coverage/lcov-report/base.css +0 -224
  19. package/coverage/lcov-report/block-navigation.js +0 -87
  20. package/coverage/lcov-report/favicon.png +0 -0
  21. package/coverage/lcov-report/index.html +0 -131
  22. package/coverage/lcov-report/prettify.css +0 -1
  23. package/coverage/lcov-report/prettify.js +0 -2
  24. package/coverage/lcov-report/sdk-node/index.html +0 -116
  25. package/coverage/lcov-report/sdk-node/index.ts.html +0 -112
  26. package/coverage/lcov-report/sdk-node/lib/console.ts.html +0 -130
  27. package/coverage/lcov-report/sdk-node/lib/grpc.ts.html +0 -178
  28. package/coverage/lcov-report/sdk-node/lib/http.ts.html +0 -190
  29. package/coverage/lcov-report/sdk-node/lib/index.html +0 -221
  30. package/coverage/lcov-report/sdk-node/lib/index.ts.html +0 -256
  31. package/coverage/lcov-report/sdk-node/lib/instrumentation.node.ts.html +0 -334
  32. package/coverage/lcov-report/sdk-node/lib/metrics.ts.html +0 -295
  33. package/coverage/lcov-report/sdk-node/lib/options.ts.html +0 -106
  34. package/coverage/lcov-report/sdk-node/lib/utils.ts.html +0 -115
  35. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  36. package/coverage/lcov-report/sorter.js +0 -196
  37. package/coverage/lcov.info +0 -312
  38. package/test-report.xml +0 -71
package/CHANGELOG.md CHANGED
@@ -1,6 +1,20 @@
1
1
  # Changelog
2
2
 
3
- ## [0.1.0-beta.4](https://github.com/ogcio/o11y/compare/@ogcio/o11y-sdk-node@v0.1.0-beta.3...@ogcio/o11y-sdk-node@v0.1.0-beta.4) (2025-01-28)
3
+ ## [0.1.0-beta.6](https://github.com/ogcio/o11y/compare/@ogcio/o11y-sdk-node@v0.1.0-beta.5...@ogcio/o11y-sdk-node@v0.1.0-beta.6) (2025-02-06)
4
+
5
+
6
+ ### Features
7
+
8
+ * remove pnpm test on prepublishOnly script ([#68](https://github.com/ogcio/o11y/issues/68)) ([41f6f57](https://github.com/ogcio/o11y/commit/41f6f57fa415c4f7adc29f49f983539274ef7320))
9
+
10
+ ## [0.1.0-beta.5](https://github.com/ogcio/o11y/compare/@ogcio/o11y-sdk-node@v0.1.0-beta.4...@ogcio/o11y-sdk-node@v0.1.0-beta.5) (2025-02-06)
11
+
12
+
13
+ ### Features
14
+
15
+ * add opentelemetry sampler ([#65](https://github.com/ogcio/o11y/issues/65)) ([66793fd](https://github.com/ogcio/o11y/commit/66793fd36bf071e592e3b455f2e33ad9d5b2db37))
16
+
17
+ ## [0.1.0-beta.4](https://github.com/ogcio/o11y/compare/@ogcio/o11y-sdk-node@v0.1.0-beta.3...@ogcio/o11y-sdk-node@v0.1.0-beta.4) (2025-01-29)
4
18
 
5
19
 
6
20
  ### Bug Fixes
@@ -25,6 +25,13 @@ interface SDKConfig {
25
25
  * @default batch
26
26
  */
27
27
  collectorMode?: SDKCollectorMode;
28
+ /**
29
+ * Array of not traced urls.
30
+ *
31
+ * @type {SamplerCondition}
32
+ * @default []
33
+ */
34
+ ignoreUrls?: SamplerCondition[];
28
35
  }
29
36
  export interface NodeSDKConfig extends SDKConfig {
30
37
  /**
@@ -40,6 +47,10 @@ export interface NodeSDKConfig extends SDKConfig {
40
47
  */
41
48
  protocol?: SDKProtocol;
42
49
  }
50
+ export interface SamplerCondition {
51
+ type: "endsWith" | "includes" | "equals";
52
+ url: string;
53
+ }
43
54
  export type SDKCollectorMode = "single" | "batch";
44
55
  export type SDKProtocol = "grpc" | "http" | "console";
45
56
  export type SDKLogLevel = "NONE" | "ERROR" | "WARN" | "INFO" | "DEBUG" | "VERBOSE" | "ALL";
@@ -1,5 +1,4 @@
1
1
  import { NodeSDK, resources } from "@opentelemetry/sdk-node";
2
- import isUrl from "is-url";
3
2
  import buildHttpExporters from "./http.js";
4
3
  import buildGrpcExporters from "./grpc.js";
5
4
  import buildConsoleExporters from "./console.js";
@@ -7,6 +6,7 @@ import { diag, DiagConsoleLogger, DiagLogLevel } from "@opentelemetry/api";
7
6
  import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
8
7
  import { W3CTraceContextPropagator } from "@opentelemetry/core";
9
8
  import packageJson from "../package.json" with { type: "json" };
9
+ import { UrlSampler } from "./url-sampler.js";
10
10
  export default function buildNodeInstrumentation(config) {
11
11
  if (!config) {
12
12
  console.warn("observability config not set. Skipping NodeJS OpenTelemetry instrumentation.");
@@ -43,6 +43,7 @@ export default function buildNodeInstrumentation(config) {
43
43
  traceExporter: exporter.traces,
44
44
  metricReader: exporter.metrics,
45
45
  logRecordProcessors: exporter.logs,
46
+ sampler: new UrlSampler(config.ignoreUrls),
46
47
  textMapPropagator: new W3CTraceContextPropagator(),
47
48
  instrumentations: [
48
49
  getNodeAutoInstrumentations({
@@ -60,3 +61,13 @@ export default function buildNodeInstrumentation(config) {
60
61
  console.error("Error starting NodeJS OpenTelemetry instrumentation:", error);
61
62
  }
62
63
  }
64
+ function isUrl(url) {
65
+ try {
66
+ new URL(url);
67
+ return true;
68
+ }
69
+ catch (err) {
70
+ console.error(err);
71
+ return false;
72
+ }
73
+ }
@@ -0,0 +1,9 @@
1
+ import { Context, SpanKind, Link, Attributes } from "@opentelemetry/api";
2
+ import { Sampler, SamplingResult } from "@opentelemetry/sdk-trace-base";
3
+ import { SamplerCondition } from "./index.js";
4
+ export declare class UrlSampler implements Sampler {
5
+ private _samplerCondition;
6
+ constructor(samplerCondition?: SamplerCondition[]);
7
+ shouldSample(_context: Context, traceId: string, _spanName: string, _spanKind: SpanKind, attributes: Attributes, _links: Link[]): SamplingResult;
8
+ toString(): string;
9
+ }
@@ -0,0 +1,28 @@
1
+ import { isValidTraceId, } from "@opentelemetry/api";
2
+ import { SamplingDecision, } from "@opentelemetry/sdk-trace-base";
3
+ export class UrlSampler {
4
+ _samplerCondition;
5
+ constructor(samplerCondition = []) {
6
+ this._samplerCondition = samplerCondition;
7
+ }
8
+ shouldSample(_context, traceId, _spanName, _spanKind, attributes, _links) {
9
+ const url = attributes["http.target"]?.toString();
10
+ if (url) {
11
+ for (const condition of this._samplerCondition) {
12
+ if ((condition.type === "equals" && url === condition.url) ||
13
+ (condition.type === "endsWith" && url.endsWith(condition.url)) ||
14
+ (condition.type === "includes" && url.includes(condition.url))) {
15
+ return { decision: SamplingDecision.NOT_RECORD };
16
+ }
17
+ }
18
+ }
19
+ return {
20
+ decision: isValidTraceId(traceId)
21
+ ? SamplingDecision.RECORD_AND_SAMPLED
22
+ : SamplingDecision.NOT_RECORD,
23
+ };
24
+ }
25
+ toString() {
26
+ throw "UrlSampler";
27
+ }
28
+ }
package/dist/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@ogcio/o11y-sdk-node",
3
- "version": "0.1.0-beta.4",
3
+ "version": "0.1.0-beta.6",
4
4
  "description": "Opentelemetry standard instrumentation SDK for NodeJS based project",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
7
7
  "scripts": {
8
8
  "build": "rm -rf dist && tsc -p tsconfig.json",
9
9
  "test": "vitest",
10
- "prepublishOnly": "pnpm i && pnpm build && pnpm test"
10
+ "prepublishOnly": "pnpm i && pnpm build"
11
11
  },
12
12
  "exports": {
13
13
  ".": "./dist/index.js",
@@ -25,7 +25,7 @@
25
25
  "license": "ISC",
26
26
  "dependencies": {
27
27
  "@opentelemetry/api": "^1.9.0",
28
- "@opentelemetry/auto-instrumentations-node": "^0.55.3",
28
+ "@opentelemetry/auto-instrumentations-node": "^0.56.0",
29
29
  "@opentelemetry/core": "1.30.1",
30
30
  "@opentelemetry/exporter-logs-otlp-grpc": "^0.57.1",
31
31
  "@opentelemetry/exporter-logs-otlp-http": "^0.57.1",
@@ -37,10 +37,9 @@
37
37
  "@opentelemetry/otlp-exporter-base": "^0.57.1",
38
38
  "@opentelemetry/sdk-metrics": "^1.30.1",
39
39
  "@opentelemetry/sdk-node": "^0.57.1",
40
- "is-url": "^1.2.4"
40
+ "@opentelemetry/sdk-trace-base": "^1.30.1"
41
41
  },
42
42
  "devDependencies": {
43
- "@types/is-url": "^1.2.32",
44
43
  "@types/node": "^22.12.0",
45
44
  "@vitest/coverage-v8": "^3.0.4",
46
45
  "tsx": "^4.19.2",
@@ -3,7 +3,7 @@ export default defineConfig({
3
3
  test: {
4
4
  globals: true,
5
5
  watch: false,
6
- include: ["**/test/*.test.ts"],
6
+ include: ["**/test/*.test.ts", "**/test/integration/*.test.ts"],
7
7
  exclude: ["**/fixtures/**", "**/dist/**"],
8
8
  poolOptions: {
9
9
  threads: {
package/lib/index.ts CHANGED
@@ -25,6 +25,13 @@ interface SDKConfig {
25
25
  * @default batch
26
26
  */
27
27
  collectorMode?: SDKCollectorMode;
28
+ /**
29
+ * Array of not traced urls.
30
+ *
31
+ * @type {SamplerCondition}
32
+ * @default []
33
+ */
34
+ ignoreUrls?: SamplerCondition[];
28
35
  }
29
36
 
30
37
  export interface NodeSDKConfig extends SDKConfig {
@@ -43,6 +50,11 @@ export interface NodeSDKConfig extends SDKConfig {
43
50
  protocol?: SDKProtocol;
44
51
  }
45
52
 
53
+ export interface SamplerCondition {
54
+ type: "endsWith" | "includes" | "equals";
55
+ url: string;
56
+ }
57
+
46
58
  export type SDKCollectorMode = "single" | "batch";
47
59
 
48
60
  export type SDKProtocol = "grpc" | "http" | "console";
@@ -1,7 +1,6 @@
1
1
  import { NodeSDK, resources } from "@opentelemetry/sdk-node";
2
2
  import type { NodeSDKConfig } from "./index.js";
3
3
  import type { Exporters } from "./options.js";
4
- import isUrl from "is-url";
5
4
  import buildHttpExporters from "./http.js";
6
5
  import buildGrpcExporters from "./grpc.js";
7
6
  import buildConsoleExporters from "./console.js";
@@ -9,6 +8,7 @@ import { diag, DiagConsoleLogger, DiagLogLevel } from "@opentelemetry/api";
9
8
  import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
10
9
  import { W3CTraceContextPropagator } from "@opentelemetry/core";
11
10
  import packageJson from "../package.json" with { type: "json" };
11
+ import { UrlSampler } from "./url-sampler.js";
12
12
 
13
13
  export default function buildNodeInstrumentation(
14
14
  config?: NodeSDKConfig,
@@ -61,6 +61,7 @@ export default function buildNodeInstrumentation(
61
61
  traceExporter: exporter.traces,
62
62
  metricReader: exporter.metrics,
63
63
  logRecordProcessors: exporter.logs,
64
+ sampler: new UrlSampler(config.ignoreUrls),
64
65
  textMapPropagator: new W3CTraceContextPropagator(),
65
66
  instrumentations: [
66
67
  getNodeAutoInstrumentations({
@@ -81,3 +82,13 @@ export default function buildNodeInstrumentation(
81
82
  );
82
83
  }
83
84
  }
85
+
86
+ function isUrl(url: string): boolean {
87
+ try {
88
+ new URL(url);
89
+ return true;
90
+ } catch (err) {
91
+ console.error(err);
92
+ return false;
93
+ }
94
+ }
@@ -0,0 +1,53 @@
1
+ import {
2
+ Context,
3
+ SpanKind,
4
+ Link,
5
+ Attributes,
6
+ isValidTraceId,
7
+ } from "@opentelemetry/api";
8
+ import {
9
+ Sampler,
10
+ SamplingDecision,
11
+ SamplingResult,
12
+ } from "@opentelemetry/sdk-trace-base";
13
+ import { SamplerCondition } from "./index.js";
14
+
15
+ export class UrlSampler implements Sampler {
16
+ private _samplerCondition: SamplerCondition[];
17
+
18
+ constructor(samplerCondition: SamplerCondition[] = []) {
19
+ this._samplerCondition = samplerCondition;
20
+ }
21
+
22
+ shouldSample(
23
+ _context: Context,
24
+ traceId: string,
25
+ _spanName: string,
26
+ _spanKind: SpanKind,
27
+ attributes: Attributes,
28
+ _links: Link[],
29
+ ): SamplingResult {
30
+ const url: string | undefined = attributes["http.target"]?.toString();
31
+
32
+ if (url) {
33
+ for (const condition of this._samplerCondition) {
34
+ if (
35
+ (condition.type === "equals" && url === condition.url) ||
36
+ (condition.type === "endsWith" && url.endsWith(condition.url)) ||
37
+ (condition.type === "includes" && url.includes(condition.url))
38
+ ) {
39
+ return { decision: SamplingDecision.NOT_RECORD };
40
+ }
41
+ }
42
+ }
43
+
44
+ return {
45
+ decision: isValidTraceId(traceId)
46
+ ? SamplingDecision.RECORD_AND_SAMPLED
47
+ : SamplingDecision.NOT_RECORD,
48
+ };
49
+ }
50
+ toString(): string {
51
+ throw "UrlSampler";
52
+ }
53
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ogcio/o11y-sdk-node",
3
- "version": "0.1.0-beta.4",
3
+ "version": "0.1.0-beta.6",
4
4
  "description": "Opentelemetry standard instrumentation SDK for NodeJS based project",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -20,7 +20,7 @@
20
20
  "license": "ISC",
21
21
  "dependencies": {
22
22
  "@opentelemetry/api": "^1.9.0",
23
- "@opentelemetry/auto-instrumentations-node": "^0.55.3",
23
+ "@opentelemetry/auto-instrumentations-node": "^0.56.0",
24
24
  "@opentelemetry/core": "1.30.1",
25
25
  "@opentelemetry/exporter-logs-otlp-grpc": "^0.57.1",
26
26
  "@opentelemetry/exporter-logs-otlp-http": "^0.57.1",
@@ -32,10 +32,9 @@
32
32
  "@opentelemetry/otlp-exporter-base": "^0.57.1",
33
33
  "@opentelemetry/sdk-metrics": "^1.30.1",
34
34
  "@opentelemetry/sdk-node": "^0.57.1",
35
- "is-url": "^1.2.4"
35
+ "@opentelemetry/sdk-trace-base": "^1.30.1"
36
36
  },
37
37
  "devDependencies": {
38
- "@types/is-url": "^1.2.32",
39
38
  "@types/node": "^22.12.0",
40
39
  "@vitest/coverage-v8": "^3.0.4",
41
40
  "tsx": "^4.19.2",
@@ -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,37 @@
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
+ continue;
27
+ }
28
+ if (parsedLine["attributes"]["http.target"] === "/api/health") {
29
+ health_traces_counter++;
30
+ }
31
+ }
32
+ }
33
+
34
+ assert.equal(health_traces_counter, 0);
35
+ assert.equal(dummy_traces_counter, 2);
36
+ });
37
+ });
@@ -0,0 +1,85 @@
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
+ --health-cmd="curl -f http://${NODE_CONTAINER_NAME}:9091/api/health > /dev/null || exit 1" \
41
+ --health-start-period=1s \
42
+ --health-retries=10 \
43
+ --health-interval=1s \
44
+ -p 9091:9091 \
45
+ ${NODE_CONTAINER_NAME}:${BUILD_ID}
46
+
47
+ COUNTER=0
48
+ echo "$NODE_CONTAINER_NAME container status"
49
+ until [ "$(docker inspect -f {{.State.Health.Status}} $NODE_CONTAINER_NAME)" = "healthy" ]; do
50
+ sleep 1
51
+ docker inspect -f {{.State.Health.Status}} $NODE_CONTAINER_NAME
52
+ COUNTER=$((COUNTER + 1))
53
+ if [ $COUNTER -ge $MAX_RETRIES ]; then
54
+ echo "Exceeded maximum retries. Exiting."
55
+ ERROR_CODE=1
56
+ break
57
+ fi
58
+ done
59
+ fi
60
+
61
+ if [[ $ERROR_CODE -eq 0 ]]; then
62
+ sleep 2
63
+ curl -X GET -f http://localhost:9091/api/dummy
64
+ sleep 2
65
+ curl -X GET -f http://localhost:9091/api/dummy
66
+ fi
67
+
68
+ # sleep N seconds to await instrumentation flow send and receiving signals
69
+ sleep 10
70
+
71
+ # Copy logs from container to file
72
+ docker container logs $ALLOY_CONTAINER_NAME >&$ROOT_PATH/packages/sdk-node/test/integration/logs.txt
73
+ echo "log file at $ROOT_PATH/packages/sdk-node/test/integration/logs.txt"
74
+
75
+ docker container stop $ALLOY_CONTAINER_NAME
76
+ docker container stop $NODE_CONTAINER_NAME
77
+
78
+ docker container rm -f $ALLOY_CONTAINER_NAME
79
+ docker container rm -f $NODE_CONTAINER_NAME
80
+
81
+ docker image rm ${NODE_CONTAINER_NAME}:${BUILD_ID}
82
+
83
+ docker network rm $NETWORK_NAME
84
+
85
+ exit $ERROR_CODE
@@ -0,0 +1,46 @@
1
+ export function parseLog(
2
+ log: string,
3
+ ): Record<string, object | string | number> {
4
+ const logArray = log
5
+ .split("\\n")
6
+ .map((line) => line.trim())
7
+ .filter((line) => line);
8
+
9
+ const jsonObject: Record<string, object | string | number> = {};
10
+ let currentSection: Record<string, object | string | number> = jsonObject;
11
+ const sectionStack: Record<string, object | string | number>[] = [];
12
+
13
+ logArray.forEach((line) => {
14
+ line = line.trim();
15
+
16
+ if (line.startsWith("->")) {
17
+ const match = line.match(/->\s+([^:]+):\s+(Str|Int)\((.+)\)/);
18
+ if (match) {
19
+ const [, key, type, value] = match;
20
+ const parsedValue = type === "Int" ? parseInt(value, 10) : value;
21
+
22
+ if (typeof currentSection === "object") {
23
+ currentSection[key] = parsedValue;
24
+ }
25
+ }
26
+ } else if (line.endsWith(":")) {
27
+ // new section
28
+ const sectionName = line
29
+ .slice(0, -1)
30
+ .trim()
31
+ .toLowerCase()
32
+ .replace(" ", "_");
33
+ jsonObject[sectionName] = {};
34
+ currentSection = jsonObject[sectionName] as Record<
35
+ string,
36
+ object | string | number
37
+ >;
38
+ sectionStack.push(currentSection);
39
+ } else if (line.startsWith('"')) {
40
+ // Additional metadata at the end, store it separately
41
+ jsonObject["metadata"] = line;
42
+ }
43
+ });
44
+
45
+ return jsonObject;
46
+ }
package/vitest.config.ts CHANGED
@@ -4,7 +4,7 @@ export default defineConfig({
4
4
  test: {
5
5
  globals: true,
6
6
  watch: false,
7
- include: ["**/test/*.test.ts"],
7
+ include: ["**/test/*.test.ts", "**/test/integration/*.test.ts"],
8
8
  exclude: ["**/fixtures/**", "**/dist/**"],
9
9
  poolOptions: {
10
10
  threads: {