@atrim/instrument-node 0.5.0-c05e3a1-20251119131235 → 0.5.1

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 CHANGED
@@ -161,6 +161,72 @@ const program = Effect.gen(function* () {
161
161
  await Effect.runPromise(program)
162
162
  ```
163
163
 
164
+ ### Effect-TS Span Annotation Helpers
165
+
166
+ The library provides 9 production-tested annotation helpers for enriching spans with semantic attributes:
167
+
168
+ ```typescript
169
+ import { Effect } from 'effect'
170
+ import {
171
+ annotateUser,
172
+ annotateBatch,
173
+ annotateDataSize,
174
+ annotateLLM,
175
+ annotateQuery,
176
+ annotateHttpRequest,
177
+ annotateError,
178
+ annotatePriority,
179
+ annotateCache,
180
+ autoEnrichSpan,
181
+ withAutoEnrichedSpan
182
+ } from '@atrim/instrument-node/effect'
183
+
184
+ // Example: Batch processing with automatic enrichment
185
+ const processBatch = Effect.gen(function* () {
186
+ // Auto-enrich with Effect metadata (fiber ID, status, parent span info)
187
+ yield* autoEnrichSpan()
188
+
189
+ // Add user context
190
+ yield* annotateUser('user-123', 'user@example.com')
191
+
192
+ // Add batch metadata
193
+ yield* annotateBatch(100, 10) // 100 items in batches of 10
194
+
195
+ // Process items
196
+ const results = yield* processItems(items)
197
+
198
+ // Update with results
199
+ yield* annotateBatch(100, 10, results.success, results.failures)
200
+
201
+ return results
202
+ }).pipe(Effect.withSpan('batch.process'))
203
+
204
+ // Or use the convenience wrapper
205
+ const processWithAutoEnrich = withAutoEnrichedSpan('batch.process')(
206
+ Effect.gen(function* () {
207
+ yield* annotateBatch(100, 10)
208
+ return yield* processItems(items)
209
+ })
210
+ )
211
+ ```
212
+
213
+ **Available annotation helpers:**
214
+ - `annotateUser(userId, email?, username?)` - User context
215
+ - `annotateDataSize(bytes, items, compressionRatio?)` - Data size metrics
216
+ - `annotateBatch(totalItems, batchSize, successCount?, failureCount?)` - Batch operations
217
+ - `annotateLLM(model, provider, tokens?)` - LLM operations (GPT, Claude, etc.)
218
+ - `annotateQuery(query, duration?, rowCount?, database?)` - Database queries
219
+ - `annotateHttpRequest(method, url, statusCode?, contentLength?)` - HTTP requests
220
+ - `annotateError(error, recoverable, errorType?)` - Error context
221
+ - `annotatePriority(priority, reason?)` - Operation priority
222
+ - `annotateCache(hit, key, ttl?)` - Cache operations
223
+
224
+ **Auto-enrichment utilities:**
225
+ - `autoEnrichSpan()` - Automatically add Effect metadata (fiber ID, status, parent span info)
226
+ - `withAutoEnrichedSpan(name, options?)` - Wrapper combining `Effect.withSpan()` + auto-enrichment
227
+
228
+ All helpers return `Effect.Effect<void>` and use `Effect.annotateCurrentSpan()` under the hood.
229
+
164
230
  ### Bun Runtime
165
231
 
166
232
  ```typescript
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atrim/instrument-node",
3
- "version": "0.5.0-c05e3a1-20251119131235",
3
+ "version": "0.5.1",
4
4
  "description": "OpenTelemetry instrumentation for Node.js with centralized YAML configuration",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -54,15 +54,15 @@
54
54
  "LICENSE"
55
55
  ],
56
56
  "dependencies": {
57
- "@effect/opentelemetry": "^0.59.0",
58
- "@effect/platform": "^0.93.0",
59
- "@effect/platform-node": "latest",
57
+ "@effect/opentelemetry": "^0.59.1",
58
+ "@effect/platform": "^0.93.6",
59
+ "@effect/platform-node": "^0.103.0",
60
60
  "@opentelemetry/auto-instrumentations-node": "^0.67.0",
61
61
  "@opentelemetry/exporter-trace-otlp-http": "^0.208.0",
62
62
  "@opentelemetry/instrumentation": "^0.208.0",
63
63
  "@opentelemetry/sdk-node": "^0.208.0",
64
64
  "@opentelemetry/sdk-trace-base": "^2.2.0",
65
- "effect": "^3.19.0",
65
+ "effect": "^3.19.8",
66
66
  "yaml": "^2.3.0",
67
67
  "zod": "^3.22.0"
68
68
  },
@@ -76,7 +76,7 @@
76
76
  "@opentelemetry/semantic-conventions": "^1.38.0",
77
77
  "@types/node": "^20.10.0",
78
78
  "@vitest/coverage-v8": "^4.0.8",
79
- "effect": "^3.19.0",
79
+ "effect": "^3.19.8",
80
80
  "testcontainers": "^11.8.1",
81
81
  "tsup": "^8.0.1",
82
82
  "tsx": "^4.7.0",
@@ -706,6 +706,15 @@ var getServiceInfoWithFallback = detectServiceInfo.pipe(
706
706
  })
707
707
  )
708
708
  );
709
+ async function detectServiceInfoAsync() {
710
+ return effect.Effect.runPromise(getServiceInfoWithFallback);
711
+ }
712
+ async function getServiceNameAsync() {
713
+ return effect.Effect.runPromise(getServiceName);
714
+ }
715
+ async function getServiceVersionAsync() {
716
+ return effect.Effect.runPromise(getServiceVersion);
717
+ }
709
718
  var NodeConfigLoaderLive = ConfigLoaderLive.pipe(
710
719
  effect.Layer.provide(effect.Layer.mergeAll(platformNode.NodeContext.layer, platform.FetchHttpClient.layer))
711
720
  );
@@ -787,7 +796,7 @@ async function loadConfigWithOptions(options = {}) {
787
796
 
788
797
  // src/core/sdk-initializer.ts
789
798
  var sdkInstance = null;
790
- var initializationDeferred = null;
799
+ var initializationPromise = null;
791
800
  function buildHttpInstrumentationConfig(options, config, _otlpEndpoint) {
792
801
  const httpConfig = { enabled: true };
793
802
  const programmaticPatterns = options.http?.ignoreOutgoingUrls || [];
@@ -903,38 +912,27 @@ function isTracingAlreadyInitialized() {
903
912
  return false;
904
913
  }
905
914
  }
906
- var initializeSdkEffect = (options = {}) => effect.Effect.gen(function* () {
915
+ async function initializeSdk(options = {}) {
907
916
  if (sdkInstance) {
908
917
  logger.warn("@atrim/instrumentation: SDK already initialized. Returning existing instance.");
909
918
  return sdkInstance;
910
919
  }
911
- if (initializationDeferred) {
920
+ if (initializationPromise) {
912
921
  logger.log(
913
- "@atrim/instrumentation: SDK initialization in progress, waiting for completion..."
922
+ "@atrim/instrumentation: SDK already initialized, waiting for initialization to complete..."
914
923
  );
915
- return yield* effect.Deferred.await(initializationDeferred);
916
- }
917
- const deferred = yield* effect.Deferred.make();
918
- initializationDeferred = deferred;
919
- const result = yield* performInitializationEffect(options).pipe(
920
- effect.Effect.tap((sdk) => effect.Deferred.succeed(deferred, sdk)),
921
- effect.Effect.tapError((error) => effect.Deferred.fail(deferred, error)),
922
- effect.Effect.ensuring(
923
- effect.Effect.sync(() => {
924
- initializationDeferred = null;
925
- })
926
- )
927
- );
928
- return result;
929
- });
930
- var performInitializationEffect = (options) => effect.Effect.gen(function* () {
931
- const config = yield* effect.Effect.tryPromise({
932
- try: () => loadConfigWithOptions(options),
933
- catch: (error) => new InitializationError2({
934
- reason: "Failed to load configuration",
935
- cause: error
936
- })
937
- });
924
+ return initializationPromise;
925
+ }
926
+ initializationPromise = performInitialization(options);
927
+ try {
928
+ const result = await initializationPromise;
929
+ return result;
930
+ } finally {
931
+ initializationPromise = null;
932
+ }
933
+ }
934
+ async function performInitialization(options) {
935
+ const config = await loadConfigWithOptions(options);
938
936
  const loggingLevel = config.instrumentation.logging || "on";
939
937
  logger.setLevel(loggingLevel);
940
938
  const alreadyInitialized = isTracingAlreadyInitialized();
@@ -950,25 +948,14 @@ var performInitializationEffect = (options) => effect.Effect.gen(function* () {
950
948
  logger.log("");
951
949
  return null;
952
950
  }
953
- const serviceInfo = yield* detectServiceInfo.pipe(
954
- effect.Effect.catchAll(
955
- () => effect.Effect.succeed({
956
- name: "unknown-service",
957
- version: void 0
958
- })
959
- )
960
- );
951
+ const serviceInfo = await detectServiceInfoAsync();
961
952
  const serviceName = options.serviceName || serviceInfo.name;
962
953
  const serviceVersion = options.serviceVersion || serviceInfo.version;
963
- const rawExporter = yield* effect.Effect.sync(() => createOtlpExporter(options.otlp));
964
- const exporter = yield* effect.Effect.sync(() => new SafeSpanExporter(rawExporter));
954
+ const rawExporter = createOtlpExporter(options.otlp);
955
+ const exporter = new SafeSpanExporter(rawExporter);
965
956
  const useSimpleProcessor = process.env.NODE_ENV === "test" || process.env.OTEL_USE_SIMPLE_PROCESSOR === "true";
966
- const baseProcessor = yield* effect.Effect.sync(
967
- () => useSimpleProcessor ? new sdkTraceBase.SimpleSpanProcessor(exporter) : new sdkTraceBase.BatchSpanProcessor(exporter)
968
- );
969
- const patternProcessor = yield* effect.Effect.sync(
970
- () => new PatternSpanProcessor(config, baseProcessor)
971
- );
957
+ const baseProcessor = useSimpleProcessor ? new sdkTraceBase.SimpleSpanProcessor(exporter) : new sdkTraceBase.BatchSpanProcessor(exporter);
958
+ const patternProcessor = new PatternSpanProcessor(config, baseProcessor);
972
959
  const instrumentations = [];
973
960
  const hasWebFramework = hasWebFrameworkInstalled();
974
961
  const enableAutoInstrumentation = shouldEnableAutoInstrumentation(
@@ -981,11 +968,15 @@ var performInitializationEffect = (options) => effect.Effect.gen(function* () {
981
968
  const undiciConfig = buildUndiciInstrumentationConfig(options, config);
982
969
  instrumentations.push(
983
970
  ...autoInstrumentationsNode.getNodeAutoInstrumentations({
971
+ // Enable HTTP instrumentation with filtering (for http/https modules)
984
972
  "@opentelemetry/instrumentation-http": httpConfig,
973
+ // Enable undici instrumentation with filtering (for fetch API)
985
974
  "@opentelemetry/instrumentation-undici": undiciConfig,
975
+ // Enable web framework instrumentations
986
976
  "@opentelemetry/instrumentation-express": { enabled: true },
987
977
  "@opentelemetry/instrumentation-fastify": { enabled: true },
988
978
  "@opentelemetry/instrumentation-koa": { enabled: true },
979
+ // Disable noisy instrumentations by default
989
980
  "@opentelemetry/instrumentation-fs": { enabled: false },
990
981
  "@opentelemetry/instrumentation-dns": { enabled: false }
991
982
  })
@@ -1013,20 +1004,18 @@ var performInitializationEffect = (options) => effect.Effect.gen(function* () {
1013
1004
  serviceName,
1014
1005
  ...serviceVersion && { serviceVersion },
1015
1006
  instrumentations,
1007
+ // Allow advanced overrides
1016
1008
  ...options.sdk
1017
1009
  };
1018
- const sdk = yield* effect.Effect.sync(() => {
1019
- const s = new sdkNode.NodeSDK(sdkConfig);
1020
- s.start();
1021
- return s;
1022
- });
1010
+ const sdk = new sdkNode.NodeSDK(sdkConfig);
1011
+ sdk.start();
1023
1012
  sdkInstance = sdk;
1024
1013
  if (!options.disableAutoShutdown) {
1025
- yield* effect.Effect.sync(() => registerShutdownHandlers(sdk));
1014
+ registerShutdownHandlers(sdk);
1026
1015
  }
1027
1016
  logInitialization(config, serviceName, serviceVersion, options, enableAutoInstrumentation);
1028
1017
  return sdk;
1029
- });
1018
+ }
1030
1019
  function getSdkInstance() {
1031
1020
  return sdkInstance;
1032
1021
  }
@@ -1039,7 +1028,7 @@ async function shutdownSdk() {
1039
1028
  }
1040
1029
  function resetSdk() {
1041
1030
  sdkInstance = null;
1042
- initializationDeferred = null;
1031
+ initializationPromise = null;
1043
1032
  }
1044
1033
  function registerShutdownHandlers(sdk) {
1045
1034
  const shutdown = async (signal) => {
@@ -1096,8 +1085,30 @@ function logInitialization(config, serviceName, serviceVersion, options, autoIns
1096
1085
  }
1097
1086
 
1098
1087
  // src/api.ts
1099
- var initializeInstrumentation = (options = {}) => effect.Effect.gen(function* () {
1100
- const sdk = yield* initializeSdkEffect(options);
1088
+ async function initializeInstrumentation(options = {}) {
1089
+ const sdk = await initializeSdk(options);
1090
+ if (sdk) {
1091
+ const config = await loadConfigWithOptions(options);
1092
+ initializePatternMatcher(config);
1093
+ }
1094
+ return sdk;
1095
+ }
1096
+ async function initializePatternMatchingOnly(options = {}) {
1097
+ const config = await loadConfigWithOptions(options);
1098
+ initializePatternMatcher(config);
1099
+ logger.log("@atrim/instrumentation: Pattern matching initialized (legacy mode)");
1100
+ logger.log(
1101
+ " Note: NodeSDK is not initialized. Use initializeInstrumentation() for complete setup."
1102
+ );
1103
+ }
1104
+ var initializeInstrumentationEffect = (options = {}) => effect.Effect.gen(function* () {
1105
+ const sdk = yield* effect.Effect.tryPromise({
1106
+ try: () => initializeSdk(options),
1107
+ catch: (error) => new InitializationError2({
1108
+ reason: "SDK initialization failed",
1109
+ cause: error
1110
+ })
1111
+ });
1101
1112
  if (sdk) {
1102
1113
  yield* effect.Effect.tryPromise({
1103
1114
  try: () => loadConfigWithOptions(options),
@@ -1115,7 +1126,7 @@ var initializeInstrumentation = (options = {}) => effect.Effect.gen(function* ()
1115
1126
  }
1116
1127
  return sdk;
1117
1128
  });
1118
- var initializePatternMatchingOnly = (options = {}) => effect.Effect.gen(function* () {
1129
+ var initializePatternMatchingOnlyEffect = (options = {}) => effect.Effect.gen(function* () {
1119
1130
  const config = yield* effect.Effect.tryPromise({
1120
1131
  try: () => loadConfigWithOptions(options),
1121
1132
  catch: (error) => new ConfigError2({
@@ -1125,7 +1136,7 @@ var initializePatternMatchingOnly = (options = {}) => effect.Effect.gen(function
1125
1136
  });
1126
1137
  yield* effect.Effect.sync(() => {
1127
1138
  initializePatternMatcher(config);
1128
- logger.log("@atrim/instrumentation: Pattern matching initialized (pattern-only mode)");
1139
+ logger.log("@atrim/instrumentation: Pattern matching initialized (legacy mode)");
1129
1140
  logger.log(
1130
1141
  " Note: NodeSDK is not initialized. Use initializeInstrumentation() for complete setup."
1131
1142
  );
@@ -1241,15 +1252,20 @@ exports.annotateDbQuery = annotateDbQuery;
1241
1252
  exports.annotateHttpRequest = annotateHttpRequest;
1242
1253
  exports.clearConfigCache = _resetConfigLoaderCache;
1243
1254
  exports.createOtlpExporter = createOtlpExporter;
1244
- exports.detectServiceInfo = detectServiceInfo;
1255
+ exports.detectServiceInfo = detectServiceInfoAsync;
1256
+ exports.detectServiceInfoEffect = detectServiceInfo;
1245
1257
  exports.getOtlpEndpoint = getOtlpEndpoint;
1246
1258
  exports.getPatternMatcher = getPatternMatcher;
1247
1259
  exports.getSdkInstance = getSdkInstance;
1248
1260
  exports.getServiceInfoWithFallback = getServiceInfoWithFallback;
1249
- exports.getServiceName = getServiceName;
1250
- exports.getServiceVersion = getServiceVersion;
1261
+ exports.getServiceName = getServiceNameAsync;
1262
+ exports.getServiceNameEffect = getServiceName;
1263
+ exports.getServiceVersion = getServiceVersionAsync;
1264
+ exports.getServiceVersionEffect = getServiceVersion;
1251
1265
  exports.initializeInstrumentation = initializeInstrumentation;
1266
+ exports.initializeInstrumentationEffect = initializeInstrumentationEffect;
1252
1267
  exports.initializePatternMatchingOnly = initializePatternMatchingOnly;
1268
+ exports.initializePatternMatchingOnlyEffect = initializePatternMatchingOnlyEffect;
1253
1269
  exports.loadConfig = loadConfig;
1254
1270
  exports.loadConfigFromInline = loadConfigFromInline;
1255
1271
  exports.loadConfigWithOptions = loadConfigWithOptions;