effect 4.0.0-beta.76 → 4.0.0-beta.78

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/dist/Config.d.ts +25 -1
  2. package/dist/Config.d.ts.map +1 -1
  3. package/dist/Config.js +32 -0
  4. package/dist/Config.js.map +1 -1
  5. package/dist/SchemaTransformation.js +1 -1
  6. package/dist/SchemaTransformation.js.map +1 -1
  7. package/dist/unstable/observability/Otlp.d.ts +18 -0
  8. package/dist/unstable/observability/Otlp.d.ts.map +1 -1
  9. package/dist/unstable/observability/Otlp.js +20 -0
  10. package/dist/unstable/observability/Otlp.js.map +1 -1
  11. package/dist/unstable/observability/OtlpLogger.d.ts +17 -1
  12. package/dist/unstable/observability/OtlpLogger.d.ts.map +1 -1
  13. package/dist/unstable/observability/OtlpLogger.js +49 -0
  14. package/dist/unstable/observability/OtlpLogger.js.map +1 -1
  15. package/dist/unstable/observability/OtlpMetrics.d.ts +14 -0
  16. package/dist/unstable/observability/OtlpMetrics.d.ts.map +1 -1
  17. package/dist/unstable/observability/OtlpMetrics.js +45 -0
  18. package/dist/unstable/observability/OtlpMetrics.js.map +1 -1
  19. package/dist/unstable/observability/OtlpResource.d.ts +3 -3
  20. package/dist/unstable/observability/OtlpResource.d.ts.map +1 -1
  21. package/dist/unstable/observability/OtlpResource.js +12 -11
  22. package/dist/unstable/observability/OtlpResource.js.map +1 -1
  23. package/dist/unstable/observability/OtlpTracer.d.ts +15 -0
  24. package/dist/unstable/observability/OtlpTracer.d.ts.map +1 -1
  25. package/dist/unstable/observability/OtlpTracer.js +45 -0
  26. package/dist/unstable/observability/OtlpTracer.js.map +1 -1
  27. package/dist/unstable/observability/internal/otlpEnv.d.ts +8 -0
  28. package/dist/unstable/observability/internal/otlpEnv.d.ts.map +1 -0
  29. package/dist/unstable/observability/internal/otlpEnv.js +16 -0
  30. package/dist/unstable/observability/internal/otlpEnv.js.map +1 -0
  31. package/dist/unstable/persistence/RateLimiter.d.ts.map +1 -1
  32. package/dist/unstable/persistence/RateLimiter.js +5 -4
  33. package/dist/unstable/persistence/RateLimiter.js.map +1 -1
  34. package/dist/unstable/persistence/Redis.d.ts.map +1 -1
  35. package/dist/unstable/persistence/Redis.js +4 -1
  36. package/dist/unstable/persistence/Redis.js.map +1 -1
  37. package/package.json +1 -1
  38. package/src/Config.ts +44 -0
  39. package/src/SchemaTransformation.ts +1 -1
  40. package/src/unstable/observability/Otlp.ts +36 -0
  41. package/src/unstable/observability/OtlpLogger.ts +56 -1
  42. package/src/unstable/observability/OtlpMetrics.ts +55 -0
  43. package/src/unstable/observability/OtlpResource.ts +27 -16
  44. package/src/unstable/observability/OtlpTracer.ts +55 -0
  45. package/src/unstable/observability/internal/otlpEnv.ts +39 -0
  46. package/src/unstable/persistence/RateLimiter.ts +6 -3
  47. package/src/unstable/persistence/Redis.ts +11 -3
@@ -1 +1 @@
1
- {"version":3,"file":"Redis.js","names":["Cache","Context","Effect","Equal","constant","identity","Hash","Schema","Redis","Service","make","fnUntraced","options","scriptCache","lookup","script","send","lua","capacity","Number","POSITIVE_INFINITY","eval_","params","flatMap","get","sha","numberOfKeys","toString","map","param","String","eval","ErrorTypeId","RedisError","ErrorClass","_tag","tag","cause","Defect","ScriptTypeId","ScriptProto","result","withReturnType","symbol","that","random","f","Object","assign","create"],"sources":["../../../src/unstable/persistence/Redis.ts"],"sourcesContent":[null],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqCA,OAAO,KAAKA,KAAK,MAAM,gBAAgB;AACvC,OAAO,KAAKC,OAAO,MAAM,kBAAkB;AAC3C,OAAO,KAAKC,MAAM,MAAM,iBAAiB;AACzC,OAAO,KAAKC,KAAK,MAAM,gBAAgB;AACvC,SAASC,QAAQ,EAAEC,QAAQ,QAAQ,mBAAmB;AACtD,OAAO,KAAKC,IAAI,MAAM,eAAe;AACrC,OAAO,KAAKC,MAAM,MAAM,iBAAiB;AAEzC;;;;;;AAMA,OAAM,MAAOC,KAAM,sBAAQP,OAAO,CAACQ,OAAO,EAStC,CAAC,0BAA0B,CAAC;AAEhC;;;;;;;;;;;AAWA,OAAO,MAAMC,IAAI,gBAAGR,MAAM,CAACS,UAAU,CAAC,WACpCC,OAEC;EAED,MAAMC,WAAW,GAAG,OAAOb,KAAK,CAACU,IAAI,CAAC;IACpCI,MAAM,EAAGC,MAAmB,IAAKH,OAAO,CAACI,IAAI,CAAS,QAAQ,EAAE,MAAM,EAAED,MAAM,CAACE,GAAG,CAAC;IACnFC,QAAQ,EAAEC,MAAM,CAACC;GAClB,CAAC;EAEF,MAAMC,KAAK,GAMTN,MAAsB,IAExB,CAAC,GAAGO,MAAwB,KAC1BpB,MAAM,CAACqB,OAAO,CAACvB,KAAK,CAACwB,GAAG,CAACX,WAAW,EAAEE,MAAM,CAAC,EAAGU,GAAG,IACjDb,OAAO,CAACI,IAAI,CACV,SAAS,EACTS,GAAG,EACHV,MAAM,CAACW,YAAY,CAAC,GAAGJ,MAAM,CAAC,CAACK,QAAQ,EAAE,EACzC,GAAGZ,MAAM,CAACO,MAAM,CAAC,GAAGA,MAAM,CAAC,CAACM,GAAG,CAAEC,KAAK,IAAKC,MAAM,CAACD,KAAK,CAAC,CAAC,CAC1D,CAAC;EAEN,OAAOxB,QAAQ,CAAmB;IAChCW,IAAI,EAAEJ,OAAO,CAACI,IAAI;IAClBe,IAAI,EAAEV;GACP,CAAC;AACJ,CAAC,CAAC;AAGF,MAAMW,WAAW,GAAgB,sCAAsC;AAEvE;;;;;;AAMA,OAAM,MAAOC,UAAW,sBAAQ1B,MAAM,CAAC2B,UAAU,CAAaF,WAAW,CAAC,CAAC;EACzEG,IAAI,eAAE5B,MAAM,CAAC6B,GAAG,CAAC,YAAY,CAAC;EAC9BC,KAAK,eAAE9B,MAAM,CAAC+B,MAAM;CACrB,CAAC;EACA;;;;;EAKS,CAACN,WAAW,IAAiBA,WAAW;;AAInD,MAAMO,YAAY,GAAiB,kCAAkC;AAoCrE,MAAMC,WAAW,GAAG;EAClB,CAACD,YAAY,GAAG;IACdjB,MAAM,EAAEjB,QAAQ;IAChBoC,MAAM,EAAEpC;GACT;EACDqC,cAAcA,CAAA;IACZ,OAAO,IAAI;EACb,CAAC;EACD,CAACvC,KAAK,CAACwC,MAAM,EAAEC,IAAa;IAC1B,OAAO,IAAI,KAAKA,IAAI;EACtB,CAAC;EACD,CAACtC,IAAI,CAACqC,MAAM,IAAC;IACX,OAAOrC,IAAI,CAACuC,MAAM,CAAC,IAAI,CAAC;EAC1B;CACD;AAED;;;;;;;;;;;AAWA,OAAO,MAAM9B,MAAM,GAAGA,CACpB+B,CAAgD,EAChDlC,OAGC,KAKDmC,MAAM,CAACC,MAAM,CAACD,MAAM,CAACE,MAAM,CAACT,WAAW,CAAC,EAAE;EACxC,GAAG5B,OAAO;EACVU,MAAM,EAAEwB,CAAC;EACTpB,YAAY,EAAE,OAAOd,OAAO,CAACc,YAAY,KAAK,QAAQ,GAAGtB,QAAQ,CAACQ,OAAO,CAACc,YAAY,CAAC,GAAGd,OAAO,CAACc;CACnG,CAAC","ignoreList":[]}
1
+ {"version":3,"file":"Redis.js","names":["Cache","Context","Effect","Equal","constant","identity","Hash","Schema","Redis","Service","make","fnUntraced","options","scriptCache","lookup","script","send","lua","capacity","Number","POSITIVE_INFINITY","eval_","params","evalSha","sha","numberOfKeys","toString","map","param","String","get","pipe","flatMap","catchIf","error","cause","includes","refresh","eval","ErrorTypeId","RedisError","ErrorClass","_tag","tag","Defect","ScriptTypeId","ScriptProto","result","withReturnType","symbol","that","random","f","Object","assign","create"],"sources":["../../../src/unstable/persistence/Redis.ts"],"sourcesContent":[null],"mappings":"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","ignoreList":[]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "effect",
3
3
  "type": "module",
4
- "version": "4.0.0-beta.76",
4
+ "version": "4.0.0-beta.78",
5
5
  "license": "MIT",
6
6
  "description": "The missing standard library for TypeScript, for writing production-grade software.",
7
7
  "homepage": "https://effect.website",
package/src/Config.ts CHANGED
@@ -82,6 +82,7 @@ import * as Predicate from "./Predicate.ts"
82
82
  import * as Rec from "./Record.ts"
83
83
  import * as Schema from "./Schema.ts"
84
84
  import * as SchemaAST from "./SchemaAST.ts"
85
+ import * as SchemaGetter from "./SchemaGetter.ts"
85
86
  import * as SchemaIssue from "./SchemaIssue.ts"
86
87
  import * as SchemaParser from "./SchemaParser.ts"
87
88
  import * as SchemaTransformation from "./SchemaTransformation.ts"
@@ -1121,6 +1122,49 @@ export const Record = <K extends Schema.Record.Key, V extends Schema.Top>(key: K
1121
1122
  return Schema.Union([record, recordString])
1122
1123
  }
1123
1124
 
1125
+ /**
1126
+ * @category schemas
1127
+ * @since 4.0.0
1128
+ */
1129
+ const ArrayConfig = <V extends Schema.Top>(value: V, options?: {
1130
+ readonly separator?: string | undefined
1131
+ }) => {
1132
+ const array = Schema.Array(value)
1133
+ const separator = options?.separator ?? ","
1134
+ const arrayString = Schema.String.pipe(
1135
+ Schema.decodeTo(
1136
+ Schema.Array(Schema.String),
1137
+ {
1138
+ decode: SchemaGetter.split(options),
1139
+ encode: SchemaGetter.transform((input: ReadonlyArray<string>) => input.join(separator))
1140
+ }
1141
+ ),
1142
+ Schema.decodeTo(array)
1143
+ )
1144
+
1145
+ return Schema.Union([arrayString, array])
1146
+ }
1147
+
1148
+ export {
1149
+ /**
1150
+ * Schema for array types that can also be parsed from a flat separated string.
1151
+ *
1152
+ * **When to use**
1153
+ *
1154
+ * Use when reading array values from a single env var, such as comma-separated
1155
+ * exporter names.
1156
+ *
1157
+ * **Details**
1158
+ *
1159
+ * Accepts either a JSON-like array from the provider or a flat string like
1160
+ * `"a,b,c"`. The `separator` defaults to `","` and can be customized.
1161
+ *
1162
+ * @category schemas
1163
+ * @since 4.0.0
1164
+ */
1165
+ ArrayConfig as Array
1166
+ }
1167
+
1124
1168
  // -----------------------------------------------------------------------------
1125
1169
  // constructors
1126
1170
  // -----------------------------------------------------------------------------
@@ -1112,7 +1112,7 @@ const encodeJsonError = (
1112
1112
  ): JsonError => {
1113
1113
  const encoded: JsonError = {
1114
1114
  name: input.name,
1115
- message: input.message
1115
+ message: typeof input.message === "string" ? input.message : ""
1116
1116
  }
1117
1117
  if (options?.includeStack && typeof input.stack === "string") {
1118
1118
  encoded.stack = input.stack
@@ -110,6 +110,42 @@ export const layer = (options: {
110
110
  )
111
111
  }
112
112
 
113
+ /**
114
+ * Creates a combined OTLP layer for logs, metrics, and traces from
115
+ * OpenTelemetry configuration.
116
+ *
117
+ * @category layers
118
+ * @since 4.0.0
119
+ */
120
+ export const layerFromConfig = (options?: {
121
+ readonly resource?: {
122
+ readonly serviceName?: string | undefined
123
+ readonly serviceVersion?: string | undefined
124
+ readonly attributes?: Record<string, unknown>
125
+ } | undefined
126
+ readonly headers?: Headers.Input | undefined
127
+ readonly tracerContext?: (<X>(primitive: Tracer.EffectPrimitive<X>, span: Tracer.AnySpan) => X) | undefined
128
+ readonly loggerExcludeLogSpans?: boolean | undefined
129
+ readonly loggerMergeWithExisting?: boolean | undefined
130
+ }): Layer.Layer<never, never, HttpClient.HttpClient | OtlpSerialization.OtlpSerialization> =>
131
+ Layer.mergeAll(
132
+ OtlpLogger.layerFromConfig({
133
+ resource: options?.resource,
134
+ headers: options?.headers,
135
+ excludeLogSpans: options?.loggerExcludeLogSpans,
136
+ mergeWithExisting: options?.loggerMergeWithExisting
137
+ }),
138
+ OtlpMetrics.layerFromConfig({
139
+ resource: options?.resource,
140
+ headers: options?.headers
141
+ }),
142
+ OtlpTracer.layerFromConfig({
143
+ resource: options?.resource,
144
+ headers: options?.headers,
145
+ context: options?.tracerContext
146
+ })
147
+ )
148
+
113
149
  /**
114
150
  * Creates the combined OTLP logs, metrics, and traces layer using JSON
115
151
  * serialization.
@@ -37,15 +37,18 @@
37
37
  import * as Arr from "../../Array.ts"
38
38
  import * as Cause from "../../Cause.ts"
39
39
  import { Clock } from "../../Clock.ts"
40
+ import * as Config from "../../Config.ts"
40
41
  import * as Duration from "../../Duration.ts"
41
42
  import * as Effect from "../../Effect.ts"
42
- import type * as Layer from "../../Layer.ts"
43
+ import * as Layer from "../../Layer.ts"
43
44
  import * as Logger from "../../Logger.ts"
44
45
  import type * as LogLevel from "../../LogLevel.ts"
46
+ import * as Option from "../../Option.ts"
45
47
  import { CurrentLogAnnotations, CurrentLogSpans } from "../../References.ts"
46
48
  import type * as Scope from "../../Scope.ts"
47
49
  import type * as Headers from "../http/Headers.ts"
48
50
  import type * as HttpClient from "../http/HttpClient.ts"
51
+ import * as OtlpEnv from "./internal/otlpEnv.ts"
49
52
  import * as Exporter from "./OtlpExporter.ts"
50
53
  import type { AnyValue, Fixed64, KeyValue, Resource } from "./OtlpResource.ts"
51
54
  import * as OtlpResource from "./OtlpResource.ts"
@@ -144,6 +147,58 @@ export const layer = (options: {
144
147
  mergeWithExisting: options.mergeWithExisting ?? true
145
148
  })
146
149
 
150
+ /**
151
+ * Creates an OTLP logs layer from OpenTelemetry configuration.
152
+ *
153
+ * @category layers
154
+ * @since 4.0.0
155
+ */
156
+ export const layerFromConfig = (options?: {
157
+ readonly resource?: {
158
+ readonly serviceName?: string | undefined
159
+ readonly serviceVersion?: string | undefined
160
+ readonly attributes?: Record<string, unknown>
161
+ } | undefined
162
+ readonly headers?: Headers.Input | undefined
163
+ readonly excludeLogSpans?: boolean | undefined
164
+ readonly mergeWithExisting?: boolean | undefined
165
+ }): Layer.Layer<never, never, HttpClient.HttpClient | OtlpSerialization> =>
166
+ Effect.gen(function*() {
167
+ const { disabled, endpoint, exporters } = yield* Config.all({
168
+ disabled: Config.boolean("OTEL_SDK_DISABLED").pipe(Config.withDefault(false)),
169
+ endpoint: OtlpEnv.endpoint("LOGS"),
170
+ exporters: OtlpEnv.exporters("LOGS")
171
+ })
172
+
173
+ if (disabled || !endpoint || !exporters.includes("otlp")) {
174
+ return Layer.empty
175
+ }
176
+
177
+ const { baseTimeout, logsTimeout, exportTimeout, scheduleDelay, maxBatchSize } = yield* Config.all({
178
+ baseTimeout: Config.option(Config.int("OTEL_EXPORTER_OTLP_TIMEOUT")),
179
+ logsTimeout: Config.option(Config.int("OTEL_EXPORTER_OTLP_LOGS_TIMEOUT")),
180
+ exportTimeout: Config.option(Config.int("OTEL_BLRP_EXPORT_TIMEOUT")),
181
+ scheduleDelay: Config.option(Config.int("OTEL_BLRP_SCHEDULE_DELAY")),
182
+ maxBatchSize: Config.option(Config.int("OTEL_BLRP_MAX_EXPORT_BATCH_SIZE"))
183
+ })
184
+
185
+ const shutdownTimeout = Option.firstSomeOf([logsTimeout, baseTimeout, exportTimeout]).pipe(
186
+ Option.map((_) => Duration.millis(_))
187
+ )
188
+ const exportInterval = Option.map(scheduleDelay, (_) => Duration.millis(_))
189
+
190
+ return layer({
191
+ url: endpoint.toString(),
192
+ resource: options?.resource,
193
+ headers: options?.headers ?? (yield* OtlpEnv.headers("LOGS")),
194
+ exportInterval: Option.getOrUndefined(exportInterval),
195
+ maxBatchSize: Option.getOrUndefined(maxBatchSize),
196
+ shutdownTimeout: Option.getOrUndefined(shutdownTimeout),
197
+ excludeLogSpans: options?.excludeLogSpans,
198
+ mergeWithExisting: options?.mergeWithExisting
199
+ })
200
+ }).pipe(Effect.orDie, Layer.unwrap)
201
+
147
202
  /**
148
203
  * OTLP logs payload serialized by `OtlpLogger`.
149
204
  *
@@ -25,14 +25,17 @@
25
25
  */
26
26
  import * as Arr from "../../Array.ts"
27
27
  import { Clock } from "../../Clock.ts"
28
+ import * as Config from "../../Config.ts"
28
29
  import * as Duration from "../../Duration.ts"
29
30
  import * as Effect from "../../Effect.ts"
30
31
  import * as Layer from "../../Layer.ts"
31
32
  import * as Metric from "../../Metric.ts"
33
+ import * as Option from "../../Option.ts"
32
34
  import type * as Scope from "../../Scope.ts"
33
35
  import type * as Headers from "../http/Headers.ts"
34
36
  import type { HttpBody } from "../http/HttpBody.ts"
35
37
  import type * as HttpClient from "../http/HttpClient.ts"
38
+ import * as OtlpEnv from "./internal/otlpEnv.ts"
36
39
  import * as Exporter from "./OtlpExporter.ts"
37
40
  import type { Fixed64, KeyValue } from "./OtlpResource.ts"
38
41
  import * as OtlpResource from "./OtlpResource.ts"
@@ -475,6 +478,58 @@ export const layer = (options: {
475
478
  readonly temporality?: AggregationTemporality | undefined
476
479
  }): Layer.Layer<never, never, HttpClient.HttpClient | OtlpSerialization> => Layer.effectDiscard(make(options))
477
480
 
481
+ /**
482
+ * Creates an OTLP metrics layer from OpenTelemetry configuration.
483
+ *
484
+ * @category layers
485
+ * @since 4.0.0
486
+ */
487
+ export const layerFromConfig = (options?: {
488
+ readonly resource?: {
489
+ readonly serviceName?: string | undefined
490
+ readonly serviceVersion?: string | undefined
491
+ readonly attributes?: Record<string, unknown>
492
+ } | undefined
493
+ readonly headers?: Headers.Input | undefined
494
+ }): Layer.Layer<never, never, HttpClient.HttpClient | OtlpSerialization> =>
495
+ Effect.gen(function*() {
496
+ const { disabled, endpoint, exporters } = yield* Config.all({
497
+ disabled: Config.boolean("OTEL_SDK_DISABLED").pipe(Config.withDefault(false)),
498
+ endpoint: OtlpEnv.endpoint("METRICS"),
499
+ exporters: OtlpEnv.exporters("METRICS")
500
+ })
501
+ if (disabled || !endpoint || !exporters.includes("otlp")) {
502
+ return Layer.empty
503
+ }
504
+
505
+ const { baseTimeout, metricsTimeout, exportTimeout, exportInterval, temporalityPreference } = yield* Config.all({
506
+ baseTimeout: Config.option(Config.int("OTEL_EXPORTER_OTLP_TIMEOUT")),
507
+ metricsTimeout: Config.option(Config.int("OTEL_EXPORTER_OTLP_METRICS_TIMEOUT")),
508
+ exportTimeout: Config.option(Config.int("OTEL_METRIC_EXPORT_TIMEOUT")),
509
+ exportInterval: Config.option(
510
+ Config.int("OTEL_METRIC_EXPORT_INTERVAL").pipe(
511
+ Config.map(Duration.millis)
512
+ )
513
+ ),
514
+ temporalityPreference: Config.option(
515
+ Config.literals(["delta", "cumulative"], "OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE")
516
+ )
517
+ })
518
+
519
+ const shutdownTimeout = Option.firstSomeOf([metricsTimeout, baseTimeout, exportTimeout]).pipe(
520
+ Option.map((_) => Duration.millis(_))
521
+ )
522
+
523
+ return layer({
524
+ url: endpoint.toString(),
525
+ resource: options?.resource,
526
+ headers: options?.headers ?? (yield* OtlpEnv.headers("METRICS")),
527
+ exportInterval: Option.getOrUndefined(exportInterval),
528
+ shutdownTimeout: Option.getOrUndefined(shutdownTimeout),
529
+ temporality: Option.getOrUndefined(temporalityPreference)
530
+ })
531
+ }).pipe(Effect.orDie, Layer.unwrap)
532
+
478
533
  /**
479
534
  * OTLP metrics payload serialized by `OtlpMetrics`.
480
535
  *
@@ -26,10 +26,10 @@
26
26
  * **Gotchas**
27
27
  *
28
28
  * `service.name` is required because the signal exporters also use it as the
29
- * instrumentation scope name. Explicit options take precedence over
30
- * environment variables. `service.name` and `service.version` are normalized
31
- * into canonical OTLP attributes instead of being left in the custom attribute
32
- * map. Unsupported runtime values are formatted as strings.
29
+ * instrumentation scope name. OpenTelemetry environment variables take
30
+ * precedence over explicit options. `service.name` and `service.version` are
31
+ * normalized into canonical OTLP attributes instead of being left in the custom
32
+ * attribute map. Unsupported runtime values are formatted as strings.
33
33
  *
34
34
  * **See also**
35
35
  *
@@ -102,9 +102,9 @@ export const make = (options: {
102
102
  *
103
103
  * **Details**
104
104
  *
105
- * Explicit options override `OTEL_RESOURCE_ATTRIBUTES`, `OTEL_SERVICE_NAME`,
106
- * and `OTEL_SERVICE_VERSION`; missing required configuration is converted to a
107
- * defect.
105
+ * `OTEL_RESOURCE_ATTRIBUTES`, `OTEL_SERVICE_NAME`, and
106
+ * `OTEL_SERVICE_VERSION` override explicit options; missing required
107
+ * configuration is converted to a defect.
108
108
  *
109
109
  * @category constructors
110
110
  * @since 4.0.0
@@ -120,19 +120,30 @@ export const fromConfig: (
120
120
  readonly serviceVersion?: string | undefined
121
121
  readonly attributes?: Record<string, unknown> | undefined
122
122
  }) {
123
+ const env = yield* Config.schema(
124
+ Schema.UndefinedOr(Config.Record(Schema.String, Schema.String)),
125
+ "OTEL_RESOURCE_ATTRIBUTES"
126
+ )
127
+
128
+ const serviceName = (yield* Config.schema(Schema.UndefinedOr(Schema.String), "OTEL_SERVICE_NAME"))
129
+ ?? env?.["service.name"] as string | undefined
130
+ ?? options?.attributes?.["service.name"] as string | undefined
131
+ ?? options?.serviceName
132
+ ?? (yield* Config.string("OTEL_SERVICE_NAME"))
133
+
134
+ const serviceVersion = (yield* Config.schema(Schema.UndefinedOr(Schema.String), "OTEL_SERVICE_VERSION"))
135
+ ?? env?.["service.version"] as string | undefined
136
+ ?? options?.attributes?.["service.version"] as string | undefined
137
+ ?? options?.serviceVersion
138
+
123
139
  const attributes = {
124
- ...(yield* Config.schema(
125
- Schema.UndefinedOr(Config.Record(Schema.String, Schema.String)),
126
- "OTEL_RESOURCE_ATTRIBUTES"
127
- )),
128
- ...options?.attributes
140
+ ...options?.attributes,
141
+ ...env
129
142
  }
130
- const serviceName = options?.serviceName ?? attributes["service.name"] as string ??
131
- (yield* Config.schema(Schema.String, "OTEL_SERVICE_NAME"))
143
+
132
144
  delete attributes["service.name"]
133
- const serviceVersion = options?.serviceVersion ?? attributes["service.version"] as string ??
134
- (yield* Config.schema(Schema.UndefinedOr(Schema.String), "OTEL_SERVICE_VERSION"))
135
145
  delete attributes["service.version"]
146
+
136
147
  return make({
137
148
  serviceName,
138
149
  serviceVersion,
@@ -24,6 +24,7 @@
24
24
  * @since 4.0.0
25
25
  */
26
26
  import * as Cause from "../../Cause.ts"
27
+ import * as Config from "../../Config.ts"
27
28
  import type * as Context from "../../Context.ts"
28
29
  import * as Duration from "../../Duration.ts"
29
30
  import * as Effect from "../../Effect.ts"
@@ -36,6 +37,7 @@ import * as Tracer from "../../Tracer.ts"
36
37
  import type { ExtractTag, Mutable } from "../../Types.ts"
37
38
  import type * as Headers from "../http/Headers.ts"
38
39
  import type * as HttpClient from "../http/HttpClient.ts"
40
+ import * as OtlpEnv from "./internal/otlpEnv.ts"
39
41
  import * as Exporter from "./OtlpExporter.ts"
40
42
  import type { KeyValue, Resource } from "./OtlpResource.ts"
41
43
  import { entriesToAttributes } from "./OtlpResource.ts"
@@ -147,6 +149,59 @@ export const layer: (options: {
147
149
  readonly shutdownTimeout?: Duration.Input | undefined
148
150
  }) => Layer.Layer<never, never, OtlpSerialization | HttpClient.HttpClient> = flow(make, Layer.effect(Tracer.Tracer))
149
151
 
152
+ /**
153
+ * Creates an OTLP traces layer from OpenTelemetry configuration.
154
+ *
155
+ * @category layers
156
+ * @since 4.0.0
157
+ */
158
+ export const layerFromConfig = (options?: {
159
+ readonly resource?: {
160
+ readonly serviceName?: string | undefined
161
+ readonly serviceVersion?: string | undefined
162
+ readonly attributes?: Record<string, unknown>
163
+ } | undefined
164
+ readonly headers?: Headers.Input | undefined
165
+ readonly context?: (<X>(primitive: Tracer.EffectPrimitive<X>, span: Tracer.AnySpan) => X) | undefined
166
+ }): Layer.Layer<never, never, HttpClient.HttpClient | OtlpSerialization> =>
167
+ Effect.gen(function*() {
168
+ const { disabled, endpoint, exporters } = yield* Config.all({
169
+ disabled: Config.boolean("OTEL_SDK_DISABLED").pipe(Config.withDefault(false)),
170
+ endpoint: OtlpEnv.endpoint("TRACES"),
171
+ exporters: OtlpEnv.exporters("TRACES")
172
+ })
173
+
174
+ if (disabled || !endpoint || !exporters.includes("otlp")) {
175
+ return Layer.empty
176
+ }
177
+
178
+ const { baseTimeout, tracesTimeout, exportTimeout, scheduleDelay, maxBatchSize } = yield* Config.all({
179
+ baseTimeout: Config.option(Config.int("OTEL_EXPORTER_OTLP_TIMEOUT")),
180
+ tracesTimeout: Config.option(Config.int("OTEL_EXPORTER_OTLP_TRACES_TIMEOUT")),
181
+ exportTimeout: Config.option(Config.int("OTEL_BSP_EXPORT_TIMEOUT")),
182
+ scheduleDelay: Config.option(
183
+ Config.int("OTEL_BSP_SCHEDULE_DELAY").pipe(
184
+ Config.map(Duration.millis)
185
+ )
186
+ ),
187
+ maxBatchSize: Config.option(Config.int("OTEL_BSP_MAX_EXPORT_BATCH_SIZE"))
188
+ })
189
+
190
+ const shutdownTimeout = Option.firstSomeOf([tracesTimeout, baseTimeout, exportTimeout]).pipe(
191
+ Option.map((_) => Duration.millis(_))
192
+ )
193
+
194
+ return layer({
195
+ url: endpoint.toString(),
196
+ resource: options?.resource,
197
+ headers: options?.headers ?? (yield* OtlpEnv.headers("TRACES")),
198
+ exportInterval: Option.getOrUndefined(scheduleDelay),
199
+ maxBatchSize: Option.getOrUndefined(maxBatchSize),
200
+ context: options?.context,
201
+ shutdownTimeout: Option.getOrUndefined(shutdownTimeout)
202
+ })
203
+ }).pipe(Effect.orDie, Layer.unwrap)
204
+
150
205
  // internal
151
206
 
152
207
  interface SpanImpl extends Tracer.Span {
@@ -0,0 +1,39 @@
1
+ import * as Config from "../../../Config.ts"
2
+ import * as Schema from "../../../Schema.ts"
3
+ import * as SchemaGetter from "../../../SchemaGetter.ts"
4
+
5
+ export type Signal = "LOGS" | "METRICS" | "TRACES"
6
+
7
+ const ExporterList = Config.Array(Schema.String).pipe(
8
+ Schema.decode({
9
+ decode: SchemaGetter.transform((_) => _.map((_) => _.toLowerCase().trim()).filter((_) => _ !== "")),
10
+ encode: SchemaGetter.passthrough()
11
+ })
12
+ )
13
+
14
+ const HeadersRecord = Config.Record(Schema.String, Schema.String)
15
+
16
+ export const headers = (signal: Signal) =>
17
+ Config.schema(HeadersRecord, `OTEL_EXPORTER_OTLP_${signal}_HEADERS`).pipe(
18
+ Config.orElse(() => Config.schema(HeadersRecord, "OTEL_EXPORTER_OTLP_HEADERS")),
19
+ Config.withDefault(undefined)
20
+ )
21
+
22
+ export const endpoint = (signal: Signal) =>
23
+ Config.url(`OTEL_EXPORTER_OTLP_${signal}_ENDPOINT`).pipe(
24
+ Config.orElse(() =>
25
+ Config.url("OTEL_EXPORTER_OTLP_ENDPOINT").pipe(
26
+ Config.map((url) => {
27
+ const slash = url.pathname.endsWith("/") ? "" : "/"
28
+ url.pathname += `${slash}v1/${signal.toLowerCase()}`
29
+ return url
30
+ })
31
+ )
32
+ ),
33
+ Config.withDefault(undefined)
34
+ )
35
+
36
+ export const exporters = (signal: Signal) =>
37
+ Config.schema(ExporterList, `OTEL_${signal}_EXPORTER`).pipe(
38
+ Config.withDefault<ReadonlyArray<string>>([])
39
+ )
@@ -630,11 +630,13 @@ export const makeStoreRedis = Effect.fnUntraced(function*(
630
630
  },
631
631
  tokenBucket(options) {
632
632
  const key = `${prefix}${options.key}`
633
+ const lastRefillKey = `${key}:refill`
633
634
  const refillMillis = Duration.toMillis(options.refillRate)
634
635
  return Effect.clockWith((clock) =>
635
636
  Effect.mapError(
636
637
  tokenBucket(
637
638
  key,
639
+ lastRefillKey,
638
640
  options.tokens,
639
641
  refillMillis,
640
642
  options.limit,
@@ -687,17 +689,18 @@ return { next, nextpttl }
687
689
  const tokenBucketScript = Redis.script(
688
690
  (
689
691
  key: string,
692
+ lastRefillKey: string,
690
693
  tokens: number,
691
694
  refillMillis: number,
692
695
  limit: number,
693
696
  now: number,
694
697
  overflow: 0 | 1
695
- ) => [key, tokens, refillMillis, limit, now, overflow],
698
+ ) => [key, lastRefillKey, tokens, refillMillis, limit, now, overflow],
696
699
  {
697
- numberOfKeys: 1,
700
+ numberOfKeys: 2,
698
701
  lua: `
699
702
  local key = KEYS[1]
700
- local last_refill_key = key .. ":refill"
703
+ local last_refill_key = KEYS[2]
701
704
  local tokens = tonumber(ARGV[1])
702
705
  local refill_ms = tonumber(ARGV[2])
703
706
  local limit = tonumber(ARGV[3])
@@ -89,14 +89,22 @@ export const make = Effect.fnUntraced(function*(
89
89
  >(
90
90
  script: Script<Config>
91
91
  ) =>
92
- (...params: Config["params"]): Effect.Effect<Config["result"], RedisError> =>
93
- Effect.flatMap(Cache.get(scriptCache, script), (sha) =>
92
+ (...params: Config["params"]): Effect.Effect<Config["result"], RedisError> => {
93
+ const evalSha = (sha: string) =>
94
94
  options.send<Config["result"]>(
95
95
  "EVALSHA",
96
96
  sha,
97
97
  script.numberOfKeys(...params).toString(),
98
98
  ...script.params(...params).map((param) => String(param))
99
- ))
99
+ )
100
+ return Cache.get(scriptCache, script).pipe(
101
+ Effect.flatMap(evalSha),
102
+ Effect.catchIf(
103
+ (error) => String(error.cause).includes("NOSCRIPT"),
104
+ () => Cache.refresh(scriptCache, script).pipe(Effect.flatMap(evalSha))
105
+ )
106
+ )
107
+ }
100
108
 
101
109
  return identity<Redis["Service"]>({
102
110
  send: options.send,