@effect-app/infra 4.0.0-beta.209 → 4.0.0-beta.210

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 (47) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/dist/Model/Repository/internal/internal.d.ts.map +1 -1
  3. package/dist/Model/Repository/internal/internal.js +28 -21
  4. package/dist/QueueMaker/SQLQueue.d.ts.map +1 -1
  5. package/dist/QueueMaker/SQLQueue.js +37 -14
  6. package/dist/QueueMaker/memQueue.d.ts.map +1 -1
  7. package/dist/QueueMaker/memQueue.js +37 -14
  8. package/dist/QueueMaker/sbqueue.d.ts.map +1 -1
  9. package/dist/QueueMaker/sbqueue.js +28 -13
  10. package/dist/RequestContext.d.ts +6 -6
  11. package/dist/RequestContext.js +7 -7
  12. package/dist/Store/Cosmos.d.ts.map +1 -1
  13. package/dist/Store/Cosmos.js +80 -66
  14. package/dist/Store/Disk.d.ts.map +1 -1
  15. package/dist/Store/Disk.js +48 -14
  16. package/dist/Store/Memory.d.ts.map +1 -1
  17. package/dist/Store/Memory.js +60 -35
  18. package/dist/Store/SQL/Pg.d.ts.map +1 -1
  19. package/dist/Store/SQL/Pg.js +80 -38
  20. package/dist/Store/SQL.d.ts.map +1 -1
  21. package/dist/Store/SQL.js +160 -77
  22. package/dist/adapters/SQL/Model.d.ts +4 -1
  23. package/dist/adapters/SQL/Model.d.ts.map +1 -1
  24. package/dist/adapters/SQL/Model.js +28 -37
  25. package/dist/api/internal/events.js +2 -2
  26. package/dist/api/routing/middleware/middleware.js +3 -3
  27. package/dist/api/routing.d.ts.map +1 -1
  28. package/dist/api/routing.js +12 -2
  29. package/dist/otel.d.ts +66 -0
  30. package/dist/otel.d.ts.map +1 -0
  31. package/dist/otel.js +56 -0
  32. package/package.json +6 -2
  33. package/src/Model/Repository/internal/internal.ts +82 -71
  34. package/src/QueueMaker/SQLQueue.ts +35 -13
  35. package/src/QueueMaker/memQueue.ts +35 -13
  36. package/src/QueueMaker/sbqueue.ts +26 -12
  37. package/src/RequestContext.ts +6 -6
  38. package/src/Store/Cosmos.ts +81 -66
  39. package/src/Store/Disk.ts +50 -16
  40. package/src/Store/Memory.ts +59 -34
  41. package/src/Store/SQL/Pg.ts +74 -40
  42. package/src/Store/SQL.ts +149 -83
  43. package/src/adapters/SQL/Model.ts +31 -36
  44. package/src/api/internal/events.ts +1 -1
  45. package/src/api/routing/middleware/middleware.ts +2 -2
  46. package/src/api/routing.ts +11 -1
  47. package/src/otel.ts +141 -0
@@ -76,7 +76,7 @@ export function makeRepoInternal<
76
76
  const encodeMany = flow(
77
77
  S.encodeEffect(S.Array(schema)),
78
78
  provideRctx,
79
- Effect.withSpan("encodeMany", { attributes: { itemType: name } }, { captureStackTrace: false })
79
+ Effect.withSpan("encodeMany", { attributes: { "app.entity": name } }, { captureStackTrace: false })
80
80
  )
81
81
  const decode = flow(S.decodeEffectConcurrently(schema), provideRctx)
82
82
  const decodeMany = flow(
@@ -140,7 +140,7 @@ export function makeRepoInternal<
140
140
  Effect.map((_: Record<string, unknown>) => _[idKey as string] as Encoded[IdKey])
141
141
  )
142
142
  const findEId = Effect.fnUntraced(function*(id: Encoded[IdKey]) {
143
- yield* Effect.annotateCurrentSpan({ itemId: id })
143
+ yield* Effect.annotateCurrentSpan({ "app.entity.id": id })
144
144
 
145
145
  return yield* Effect.flatMap(
146
146
  store.find(id),
@@ -153,7 +153,7 @@ export function makeRepoInternal<
153
153
  })
154
154
  // TODO: select the particular field, instead of as struct
155
155
  const findE = Effect.fnUntraced(function*(id: T[IdKey]) {
156
- yield* Effect.annotateCurrentSpan({ itemId: id })
156
+ yield* Effect.annotateCurrentSpan({ "app.entity.id": id })
157
157
 
158
158
  return yield* pipe(
159
159
  encodeId({ [idKey]: id } as any),
@@ -163,8 +163,8 @@ export function makeRepoInternal<
163
163
  )
164
164
  })
165
165
 
166
- const find = Effect.fn("find", { attributes: { itemType: name } })(function*(id: T[IdKey]) {
167
- yield* Effect.annotateCurrentSpan({ itemId: id })
166
+ const find = Effect.fn("find", { attributes: { "app.entity": name } })(function*(id: T[IdKey]) {
167
+ yield* Effect.annotateCurrentSpan({ "app.entity.id": id })
168
168
 
169
169
  return yield* flatMapOption(findE(id), (_) => Effect.orDie(decode(_)))
170
170
  })
@@ -190,11 +190,14 @@ export function makeRepoInternal<
190
190
  Effect.andThen(saveAllE)
191
191
  )
192
192
 
193
- const saveAndPublish = Effect.fn("saveAndPublish", { attributes: { itemType: name } })(
193
+ const saveAndPublish = Effect.fn("saveAndPublish", { attributes: { "app.entity": name } })(
194
194
  function*(items: Iterable<T>, events: Iterable<Evt> = []) {
195
195
  const it = Chunk.fromIterable(items)
196
196
  const evts = [...events]
197
- yield* Effect.annotateCurrentSpan({ itemIds: Chunk.map(it, (_) => _[idKey]), events: evts.length })
197
+ yield* Effect.annotateCurrentSpan({
198
+ "app.entity.ids": Chunk.map(it, (_) => _[idKey]),
199
+ "app.event.count": evts.length
200
+ })
198
201
  return yield* saveAll(it)
199
202
  .pipe(
200
203
  Effect.andThen(Effect.sync(() => toNonEmptyArray(evts))),
@@ -206,12 +209,15 @@ export function makeRepoInternal<
206
209
  }
207
210
  )
208
211
 
209
- const removeAndPublish = Effect.fn("removeAndPublish", { attributes: { itemType: name } })(
212
+ const removeAndPublish = Effect.fn("removeAndPublish", { attributes: { "app.entity": name } })(
210
213
  function*(a: Iterable<T>, events: Iterable<Evt> = []) {
211
214
  const { set } = yield* cms
212
215
  const it = [...a]
213
216
  const evts = [...events]
214
- yield* Effect.annotateCurrentSpan({ itemIds: it.map((_) => _[idKey]), eventCount: evts.length })
217
+ yield* Effect.annotateCurrentSpan({
218
+ "app.entity.ids": it.map((_) => _[idKey]),
219
+ "app.event.count": evts.length
220
+ })
215
221
  const items = yield* encodeMany(it).pipe(Effect.orDie)
216
222
  if (Array.isReadonlyArrayNonEmpty(items)) {
217
223
  yield* store.batchRemove(
@@ -231,7 +237,7 @@ export function makeRepoInternal<
231
237
  }
232
238
  )
233
239
 
234
- const removeById = Effect.fn("removeById", { attributes: { itemType: name } })(
240
+ const removeById = Effect.fn("removeById", { attributes: { "app.entity": name } })(
235
241
  function*(idOrIds: T[IdKey] | ReadonlyArray<T[IdKey]>) {
236
242
  const ids = globalThis.Array.isArray(idOrIds)
237
243
  ? idOrIds as readonly T[IdKey][]
@@ -241,7 +247,7 @@ export function makeRepoInternal<
241
247
  }
242
248
  const { set } = yield* cms
243
249
  const eids = yield* Effect.forEach(ids, (_) => encodeIdOnly(_ as any)).pipe(Effect.orDie)
244
- yield* Effect.annotateCurrentSpan({ itemIds: eids })
250
+ yield* Effect.annotateCurrentSpan({ "app.entity.ids": eids })
245
251
  yield* store.batchRemove(eids)
246
252
  for (const id of eids) {
247
253
  set(id, undefined)
@@ -250,11 +256,13 @@ export function makeRepoInternal<
250
256
  }
251
257
  )
252
258
 
253
- const parseMany = Effect.fn("parseMany", { attributes: { itemType: name } })(function*(items: readonly PM[]) {
254
- const cm = yield* cms
255
- return yield* decodeMany(items.map((_) => mapReverse(_, cm.set))).pipe(Effect.orDie)
256
- })
257
- const parseMany2 = Effect.fn("parseMany2", { attributes: { itemType: name } })(
259
+ const parseMany = Effect.fn("parseMany", { attributes: { "app.entity": name } })(
260
+ function*(items: readonly PM[]) {
261
+ const cm = yield* cms
262
+ return yield* decodeMany(items.map((_) => mapReverse(_, cm.set))).pipe(Effect.orDie)
263
+ }
264
+ )
265
+ const parseMany2 = Effect.fn("parseMany2", { attributes: { "app.entity": name } })(
258
266
  function*<A, R>(items: readonly PM[], schema: S.Codec<A, Encoded, R>) {
259
267
  const cm = yield* cms
260
268
  return yield* S.decodeEffectConcurrently(S.Array(schema))(items.map((_) => mapReverse(_, cm.set))).pipe(
@@ -332,73 +340,76 @@ export function makeRepoInternal<
332
340
  .map(eff, (_) => NonNegativeInt(_.length))
333
341
  .pipe(Effect.catchTag("SchemaError", (e) => Effect.die(e)))
334
342
  : eff,
335
- Effect.withSpan("Repository.query [effect-app/infra]", {
343
+ Effect.withSpan(`query ${name}`, {
344
+ kind: "client",
336
345
  attributes: {
337
- itemType: name,
338
- "repository.model_name": name,
339
- query: { ...a, schema: a.schema ? "__SCHEMA__" : a.schema, filter: a.filter }
346
+ "app.entity": name,
347
+ "db.operation.name": "query",
348
+ "db.collection.name": name
340
349
  }
341
350
  }, { captureStackTrace: false })
342
351
  )
343
352
  }) as any
344
353
 
345
- const validateSample = Effect.fn("validateSample", { attributes: { itemType: name } })(function*(options?: {
346
- percentage?: number
347
- maxItems?: number
348
- }) {
349
- const percentage = options?.percentage ?? 0.1 // default 10%
350
- const maxItems = options?.maxItems
351
-
352
- // 1. get all IDs with projection (bypasses main schema decode)
353
- const allIds = yield* store.filter({
354
- t: null as unknown as Encoded,
355
- select: [idKey as keyof Encoded]
356
- })
357
-
358
- // 2. random subset
359
- const shuffled = [...allIds].sort(() => Math.random() - 0.5)
360
- const sampleSize = Math.min(
361
- maxItems ?? Infinity,
362
- Math.ceil(allIds.length * percentage)
363
- )
364
- const sample = shuffled.slice(0, sampleSize)
365
-
366
- // 3. validate each item
367
- const errors: ValidationError[] = []
354
+ const validateSample = Effect.fn("validateSample", { attributes: { "app.entity": name } })(
355
+ function*(options?: {
356
+ percentage?: number
357
+ maxItems?: number
358
+ }) {
359
+ const percentage = options?.percentage ?? 0.1 // default 10%
360
+ const maxItems = options?.maxItems
361
+
362
+ // 1. get all IDs with projection (bypasses main schema decode)
363
+ const allIds = yield* store.filter({
364
+ t: null as unknown as Encoded,
365
+ select: [idKey as keyof Encoded]
366
+ })
367
+
368
+ // 2. random subset
369
+ const shuffled = [...allIds].sort(() => Math.random() - 0.5)
370
+ const sampleSize = Math.min(
371
+ maxItems ?? Infinity,
372
+ Math.ceil(allIds.length * percentage)
373
+ )
374
+ const sample = shuffled.slice(0, sampleSize)
368
375
 
369
- for (const item of sample) {
370
- const id = item[idKey]
371
- const rawResult = yield* store.find(id)
376
+ // 3. validate each item
377
+ const errors: ValidationError[] = []
372
378
 
373
- if (Option.isNone(rawResult)) continue
379
+ for (const item of sample) {
380
+ const id = item[idKey]
381
+ const rawResult = yield* store.find(id)
374
382
 
375
- const rawData = rawResult.value as Encoded
376
- const jitMResult = mapFrom(rawData) // apply jitM
383
+ if (Option.isNone(rawResult)) continue
377
384
 
378
- const decodeResult = yield* S.decodeEffectConcurrently(schema)(jitMResult).pipe(
379
- Effect.result,
380
- provideRctx
381
- )
385
+ const rawData = rawResult.value as Encoded
386
+ const jitMResult = mapFrom(rawData) // apply jitM
382
387
 
383
- if (Result.isFailure(decodeResult)) {
384
- errors.push(
385
- ValidationError.make({
386
- id,
387
- rawData,
388
- jitMResult,
389
- error: decodeResult.failure
390
- })
388
+ const decodeResult = yield* S.decodeEffectConcurrently(schema)(jitMResult).pipe(
389
+ Effect.result,
390
+ provideRctx
391
391
  )
392
+
393
+ if (Result.isFailure(decodeResult)) {
394
+ errors.push(
395
+ ValidationError.make({
396
+ id,
397
+ rawData,
398
+ jitMResult,
399
+ error: decodeResult.failure
400
+ })
401
+ )
402
+ }
392
403
  }
393
- }
394
404
 
395
- return ValidationResult.make({
396
- total: NonNegativeInt(allIds.length),
397
- sampled: NonNegativeInt(sample.length),
398
- valid: NonNegativeInt(sample.length - errors.length),
399
- errors
400
- })
401
- })
405
+ return ValidationResult.make({
406
+ total: NonNegativeInt(allIds.length),
407
+ sampled: NonNegativeInt(sample.length),
408
+ valid: NonNegativeInt(sample.length - errors.length),
409
+ errors
410
+ })
411
+ }
412
+ )
402
413
 
403
414
  const r = {
404
415
  changeFeed,
@@ -450,7 +461,7 @@ export function makeRepoInternal<
450
461
  // },
451
462
  save: (...xes: any[]) =>
452
463
  Effect.flatMap(encMany(xes), (_) => saveAllE(_)).pipe(
453
- Effect.withSpan("mapped.save", { attributes: { itemType: name } }, { captureStackTrace: false })
464
+ Effect.withSpan("mapped.save", { attributes: { "app.entity": name } }, { captureStackTrace: false })
454
465
  )
455
466
  }
456
467
  }
@@ -522,7 +533,7 @@ export function makeStore<Encoded extends FieldValues>() {
522
533
  .pipe(
523
534
  Effect.flatMap(Effect.forEach(encodeToEncoded())),
524
535
  setupRequestContextFromCurrent("Repository.makeInitial [effect-app/infra]", {
525
- attributes: { "repository.model_name": name }
536
+ attributes: { "app.entity": name }
526
537
  })
527
538
  )
528
539
  : undefined,
@@ -8,6 +8,7 @@ import { pretty } from "effect-app/utils"
8
8
  import { SqlClient } from "effect/unstable/sql"
9
9
  import { SQLModel } from "../adapters/SQL.js"
10
10
  import { InfraLogger } from "../logger.js"
11
+ import { messagingSpanArgs } from "../otel.js"
11
12
 
12
13
  export const QueueId = S.Finite.pipe(S.brand("QueueId"))
13
14
  export type QueueId = typeof QueueId.Type
@@ -101,10 +102,20 @@ export const makeSQLQueue = Effect.fnUntraced(function*<
101
102
  })
102
103
  }
103
104
  const queue = {
104
- publish: Effect.fn("queue.publish: " + queueName, { kind: "producer" })(function*(
105
+ publish: Effect.fn(`publish ${queueName}`, {
106
+ kind: "producer",
107
+ attributes: {
108
+ "messaging.system": "sql",
109
+ "messaging.operation.name": "publish",
110
+ "messaging.destination.name": queueName
111
+ }
112
+ })(function*(
105
113
  ...messages: NonEmptyReadonlyArray<Evt>
106
114
  ) {
107
- yield* Effect.annotateCurrentSpan({ "message_tags": messages.map((_) => _._tag) })
115
+ yield* Effect.annotateCurrentSpan({
116
+ "messaging.batch.message_count": messages.length,
117
+ "messaging.message.types": messages.map((_) => _._tag)
118
+ })
108
119
  const requestContext = yield* getRequestContext
109
120
  yield* Effect.forEach(messages, (m) => q.offer(m, requestContext), { discard: true })
110
121
  }),
@@ -120,21 +131,26 @@ export const makeSQLQueue = Effect.fnUntraced(function*<
120
131
  Effect.annotateLogs({ body: pretty(body), meta: pretty(meta) }),
121
132
  Effect.andThen(handleEvent(body)),
122
133
  silenceAndReportError,
123
- (_) =>
124
- setupRequestContextWithCustomSpan(
134
+ (_) => {
135
+ const args = messagingSpanArgs({
136
+ operation: "process",
137
+ system: "sql",
138
+ destination: queueDrainName,
139
+ messageId: body.id,
140
+ conversationId: sessionId,
141
+ extra: { "messaging.message.type": body._tag, "messaging.message.body": body }
142
+ }, "consumer")
143
+ return setupRequestContextWithCustomSpan(
125
144
  _,
126
145
  meta,
127
- `queue.drain: ${queueDrainName}.${body._tag}`,
146
+ args.name,
128
147
  {
129
148
  captureStackTrace: false,
130
- kind: "consumer",
131
- attributes: {
132
- "queue.name": queueDrainName,
133
- "queue.sessionId": sessionId,
134
- "queue.input": body
135
- }
149
+ kind: args.kind,
150
+ attributes: args.attributes
136
151
  }
137
152
  )
153
+ }
138
154
  )
139
155
  if (meta.span) {
140
156
  effect = Effect.withParentSpan(effect, Tracer.externalSpan(meta.span))
@@ -142,8 +158,14 @@ export const makeSQLQueue = Effect.fnUntraced(function*<
142
158
  return yield* effect
143
159
  })
144
160
 
145
- return Effect.fn(`queue.drain: ${queueDrainName}`, {
146
- attributes: { "queue.type": "sql", "queue.name": queueDrainName, "queue.sessionId": sessionId }
161
+ return Effect.fn(`receive ${queueDrainName}`, {
162
+ kind: "consumer",
163
+ attributes: {
164
+ "messaging.system": "sql",
165
+ "messaging.operation.name": "receive",
166
+ "messaging.destination.name": queueDrainName,
167
+ ...(sessionId !== undefined && { "messaging.message.conversation_id": sessionId })
168
+ }
147
169
  })(function*() {
148
170
  const x = yield* q.take
149
171
  yield* processMessage(x).pipe(
@@ -5,6 +5,7 @@ import * as Q from "effect/Queue"
5
5
  import { MemQueue } from "../adapters/memQueue.js"
6
6
  import { getRequestContext, setupRequestContextWithCustomSpan } from "../api/setupRequest.js"
7
7
  import { InfraLogger } from "../logger.js"
8
+ import { messagingSpanArgs } from "../otel.js"
8
9
  import { reportNonInterruptedFailure, reportNonInterruptedFailureCause } from "./errors.js"
9
10
  import { QueueMeta } from "./service.js"
10
11
 
@@ -32,10 +33,20 @@ export const makeMemQueue = Effect.fnUntraced(function*<
32
33
  const parseDrain = flow(S.decodeUnknownEffectConcurrently(drainWJson), Effect.orDie)
33
34
 
34
35
  const queue = {
35
- publish: Effect.fn("queue.publish: " + queueName, { kind: "producer" })(function*(
36
+ publish: Effect.fn(`publish ${queueName}`, {
37
+ kind: "producer",
38
+ attributes: {
39
+ "messaging.system": "memory",
40
+ "messaging.operation.name": "publish",
41
+ "messaging.destination.name": queueName
42
+ }
43
+ })(function*(
36
44
  ...messages: NonEmptyReadonlyArray<Evt>
37
45
  ) {
38
- yield* Effect.annotateCurrentSpan({ "message_tags": messages.map((_) => _._tag) })
46
+ yield* Effect.annotateCurrentSpan({
47
+ "messaging.batch.message_count": messages.length,
48
+ "messaging.message.types": messages.map((_) => _._tag)
49
+ })
39
50
  const requestContext = yield* getRequestContext
40
51
  // we JSON encode, because that is what the wire also does, and it reveals holes in e.g unknown encoders (Date->String)
41
52
  yield* Effect.forEach(
@@ -63,29 +74,40 @@ export const makeMemQueue = Effect.fnUntraced(function*<
63
74
  Effect.annotateLogs({ body: pretty(body), meta: pretty(meta) }),
64
75
  Effect.andThen(handleEvent(body)),
65
76
  silenceAndReportError,
66
- (_) =>
67
- setupRequestContextWithCustomSpan(
77
+ (_) => {
78
+ const args = messagingSpanArgs({
79
+ operation: "process",
80
+ system: "memory",
81
+ destination: queueDrainName,
82
+ messageId: body.id,
83
+ conversationId: sessionId,
84
+ extra: { "messaging.message.type": body._tag, "messaging.message.body": body }
85
+ }, "consumer")
86
+ return setupRequestContextWithCustomSpan(
68
87
  _,
69
88
  meta,
70
- `queue.drain: ${queueDrainName}.${body._tag}`,
89
+ args.name,
71
90
  {
72
91
  captureStackTrace: false,
73
- kind: "consumer",
74
- attributes: {
75
- "queue.name": queueDrainName,
76
- "queue.sessionId": sessionId,
77
- "queue.input": body
78
- }
92
+ kind: args.kind,
93
+ attributes: args.attributes
79
94
  }
80
95
  )
96
+ }
81
97
  )
82
98
  if (meta.span) {
83
99
  effect = Effect.withParentSpan(effect, Tracer.externalSpan(meta.span))
84
100
  }
85
101
  return yield* effect
86
102
  })
87
- return Effect.fn(`queue.drain: ${queueDrainName}`, {
88
- attributes: { "queue.type": "mem", "queue.name": queueDrainName, "queue.sessionId": sessionId }
103
+ return Effect.fn(`receive ${queueDrainName}`, {
104
+ kind: "consumer",
105
+ attributes: {
106
+ "messaging.system": "memory",
107
+ "messaging.operation.name": "receive",
108
+ "messaging.destination.name": queueDrainName,
109
+ ...(sessionId !== undefined && { "messaging.message.conversation_id": sessionId })
110
+ }
89
111
  })(function*() {
90
112
  const x = yield* Q.take(qDrain)
91
113
  const exit = yield* processMessage(x).pipe(
@@ -5,6 +5,7 @@ import { pretty } from "effect-app/utils"
5
5
  import { Receiver, Sender } from "../adapters/ServiceBus.js"
6
6
  import { getRequestContext, setupRequestContextWithCustomSpan } from "../api/setupRequest.js"
7
7
  import { InfraLogger } from "../logger.js"
8
+ import { messagingSpanArgs } from "../otel.js"
8
9
  import { reportNonInterruptedFailure, reportNonInterruptedFailureCause, reportQueueError } from "./errors.js"
9
10
  import { QueueMeta } from "./service.js"
10
11
 
@@ -52,21 +53,26 @@ export function makeServiceBusQueue<
52
53
  Effect.orDie,
53
54
  // we silenceAndReportError here, so that the error is reported, and moves into the Exit.
54
55
  silenceAndReportError,
55
- (_) =>
56
- setupRequestContextWithCustomSpan(
56
+ (_) => {
57
+ const args = messagingSpanArgs({
58
+ operation: "process",
59
+ system: "servicebus",
60
+ destination: receiver.name,
61
+ messageId: body.id,
62
+ conversationId: sessionId,
63
+ extra: { "messaging.message.type": body._tag, "messaging.message.body": body }
64
+ }, "consumer")
65
+ return setupRequestContextWithCustomSpan(
57
66
  _,
58
67
  meta,
59
- `queue.drain: ${receiver.name}${sessionId ? `#${sessionId}` : ""}.${body._tag}`,
68
+ args.name,
60
69
  {
61
70
  captureStackTrace: false,
62
- kind: "consumer",
63
- attributes: {
64
- "queue.name": receiver.name,
65
- "queue.sessionId": sessionId,
66
- "queue.input": body
67
- }
71
+ kind: args.kind,
72
+ attributes: args.attributes
68
73
  }
69
74
  )
75
+ }
70
76
  )
71
77
  if (meta.span) {
72
78
  effect = Effect.withParentSpan(effect, Tracer.externalSpan(meta.span))
@@ -84,10 +90,18 @@ export function makeServiceBusQueue<
84
90
  .pipe(Effect.andThen(Effect.never))
85
91
  },
86
92
 
87
- publish: Effect.fn("queue.publish: " + sender.name, {
88
- kind: "producer"
93
+ publish: Effect.fn(`publish ${sender.name}`, {
94
+ kind: "producer",
95
+ attributes: {
96
+ "messaging.system": "servicebus",
97
+ "messaging.operation.name": "publish",
98
+ "messaging.destination.name": sender.name
99
+ }
89
100
  })(function*(...messages: NonEmptyReadonlyArray<Evt>) {
90
- yield* Effect.annotateCurrentSpan({ "message_tags": messages.map((_) => _._tag) })
101
+ yield* Effect.annotateCurrentSpan({
102
+ "messaging.batch.message_count": messages.length,
103
+ "messaging.message.types": messages.map((_) => _._tag)
104
+ })
91
105
  const requestContext = yield* getRequestContext
92
106
  const msgs = yield* Effect.forEach(messages, (m) =>
93
107
  encodePublish({ body: m, meta: requestContext }).pipe(
@@ -34,16 +34,16 @@ export class RequestContext extends S.Opaque<
34
34
  }
35
35
 
36
36
  export const spanAttributes = (ctx: Pick<RequestContext, "locale" | "namespace"> & Partial<RequestContext>) => ({
37
- "request.name": ctx.name,
38
- "request.locale": ctx.locale,
39
- "request.namespace": ctx.namespace,
40
- ...ctx.sourceId ? { "request.source.id": ctx.sourceId } : {},
37
+ "code.function.name": ctx.name,
38
+ "app.locale": ctx.locale,
39
+ "app.tenant.id": ctx.namespace,
40
+ ...ctx.sourceId ? { "client.id": ctx.sourceId } : {},
41
41
  ...(ctx.userProfile?.sub
42
42
  ? {
43
- "request.user.sub": ctx
43
+ "user.id": ctx
44
44
  .userProfile
45
45
  .sub,
46
- "request.user.roles": "roles" in ctx
46
+ "user.roles": "roles" in ctx
47
47
  .userProfile
48
48
  ? ctx.userProfile.roles
49
49
  : undefined