@uploadista/observability 0.0.3

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 (118) hide show
  1. package/.turbo/turbo-build.log +5 -0
  2. package/.turbo/turbo-check.log +0 -0
  3. package/LICENSE +21 -0
  4. package/dist/core/errors.d.ts +8 -0
  5. package/dist/core/errors.d.ts.map +1 -0
  6. package/dist/core/errors.js +108 -0
  7. package/dist/core/index.d.ts +8 -0
  8. package/dist/core/index.d.ts.map +1 -0
  9. package/dist/core/index.js +8 -0
  10. package/dist/core/layers.d.ts +104 -0
  11. package/dist/core/layers.d.ts.map +1 -0
  12. package/dist/core/layers.js +110 -0
  13. package/dist/core/logging.d.ts +18 -0
  14. package/dist/core/logging.d.ts.map +1 -0
  15. package/dist/core/logging.js +41 -0
  16. package/dist/core/metrics.d.ts +37 -0
  17. package/dist/core/metrics.d.ts.map +1 -0
  18. package/dist/core/metrics.js +72 -0
  19. package/dist/core/testing.d.ts +43 -0
  20. package/dist/core/testing.d.ts.map +1 -0
  21. package/dist/core/testing.js +93 -0
  22. package/dist/core/tracing.d.ts +19 -0
  23. package/dist/core/tracing.d.ts.map +1 -0
  24. package/dist/core/tracing.js +43 -0
  25. package/dist/core/utilities.d.ts +11 -0
  26. package/dist/core/utilities.d.ts.map +1 -0
  27. package/dist/core/utilities.js +41 -0
  28. package/dist/flow/errors.d.ts +15 -0
  29. package/dist/flow/errors.d.ts.map +1 -0
  30. package/dist/flow/errors.js +66 -0
  31. package/dist/flow/index.d.ts +6 -0
  32. package/dist/flow/index.d.ts.map +1 -0
  33. package/dist/flow/index.js +6 -0
  34. package/dist/flow/layers.d.ts +40 -0
  35. package/dist/flow/layers.d.ts.map +1 -0
  36. package/dist/flow/layers.js +94 -0
  37. package/dist/flow/metrics.d.ts +52 -0
  38. package/dist/flow/metrics.d.ts.map +1 -0
  39. package/dist/flow/metrics.js +89 -0
  40. package/dist/flow/testing.d.ts +11 -0
  41. package/dist/flow/testing.d.ts.map +1 -0
  42. package/dist/flow/testing.js +27 -0
  43. package/dist/flow/tracing.d.ts +35 -0
  44. package/dist/flow/tracing.d.ts.map +1 -0
  45. package/dist/flow/tracing.js +42 -0
  46. package/dist/index.d.ts +8 -0
  47. package/dist/index.d.ts.map +1 -0
  48. package/dist/index.js +14 -0
  49. package/dist/service/metrics.d.ts +23 -0
  50. package/dist/service/metrics.d.ts.map +1 -0
  51. package/dist/service/metrics.js +17 -0
  52. package/dist/storage/azure.d.ts +47 -0
  53. package/dist/storage/azure.d.ts.map +1 -0
  54. package/dist/storage/azure.js +89 -0
  55. package/dist/storage/filesystem.d.ts +47 -0
  56. package/dist/storage/filesystem.d.ts.map +1 -0
  57. package/dist/storage/filesystem.js +70 -0
  58. package/dist/storage/gcs.d.ts +47 -0
  59. package/dist/storage/gcs.d.ts.map +1 -0
  60. package/dist/storage/gcs.js +90 -0
  61. package/dist/storage/index.d.ts +5 -0
  62. package/dist/storage/index.d.ts.map +1 -0
  63. package/dist/storage/index.js +5 -0
  64. package/dist/storage/s3.d.ts +47 -0
  65. package/dist/storage/s3.d.ts.map +1 -0
  66. package/dist/storage/s3.js +67 -0
  67. package/dist/test-observability.d.ts +12 -0
  68. package/dist/test-observability.d.ts.map +1 -0
  69. package/dist/test-observability.js +153 -0
  70. package/dist/upload/errors.d.ts +16 -0
  71. package/dist/upload/errors.d.ts.map +1 -0
  72. package/dist/upload/errors.js +107 -0
  73. package/dist/upload/index.d.ts +6 -0
  74. package/dist/upload/index.d.ts.map +1 -0
  75. package/dist/upload/index.js +6 -0
  76. package/dist/upload/layers.d.ts +32 -0
  77. package/dist/upload/layers.d.ts.map +1 -0
  78. package/dist/upload/layers.js +63 -0
  79. package/dist/upload/metrics.d.ts +46 -0
  80. package/dist/upload/metrics.d.ts.map +1 -0
  81. package/dist/upload/metrics.js +80 -0
  82. package/dist/upload/testing.d.ts +32 -0
  83. package/dist/upload/testing.d.ts.map +1 -0
  84. package/dist/upload/testing.js +52 -0
  85. package/dist/upload/tracing.d.ts +25 -0
  86. package/dist/upload/tracing.d.ts.map +1 -0
  87. package/dist/upload/tracing.js +35 -0
  88. package/package.json +37 -0
  89. package/src/core/errors.ts +187 -0
  90. package/src/core/index.ts +9 -0
  91. package/src/core/layers.ts +205 -0
  92. package/src/core/logging.ts +81 -0
  93. package/src/core/metrics.ts +108 -0
  94. package/src/core/testing.ts +142 -0
  95. package/src/core/tracing.ts +67 -0
  96. package/src/core/utilities.ts +133 -0
  97. package/src/flow/errors.ts +95 -0
  98. package/src/flow/index.ts +17 -0
  99. package/src/flow/layers.ts +131 -0
  100. package/src/flow/metrics.ts +130 -0
  101. package/src/flow/testing.ts +33 -0
  102. package/src/flow/tracing.ts +72 -0
  103. package/src/index.ts +31 -0
  104. package/src/service/metrics.ts +31 -0
  105. package/src/storage/azure.ts +163 -0
  106. package/src/storage/filesystem.ts +153 -0
  107. package/src/storage/gcs.ts +161 -0
  108. package/src/storage/index.ts +5 -0
  109. package/src/storage/s3.ts +136 -0
  110. package/src/test-observability.ts +234 -0
  111. package/src/upload/errors.ts +166 -0
  112. package/src/upload/index.ts +12 -0
  113. package/src/upload/layers.ts +88 -0
  114. package/src/upload/metrics.ts +118 -0
  115. package/src/upload/testing.ts +60 -0
  116. package/src/upload/tracing.ts +58 -0
  117. package/tsconfig.json +10 -0
  118. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,131 @@
1
+ import { Effect, Layer, Metric } from "effect";
2
+ import {
3
+ FlowObservability,
4
+ makeFlowObservabilityLayer,
5
+ } from "../core/layers.js";
6
+ import { createFlowMetrics, type FlowMetrics } from "./metrics.js";
7
+
8
+ // ============================================================================
9
+ // Flow Observability Layer Implementation
10
+ // ============================================================================
11
+
12
+ /**
13
+ * Create a live flow observability layer with full metrics
14
+ */
15
+ export const makeFlowObservabilityLive = (
16
+ serviceName = "uploadista-flow-engine",
17
+ ): Layer.Layer<FlowObservability> => {
18
+ const metrics = createFlowMetrics();
19
+
20
+ return Layer.succeed(FlowObservability, {
21
+ serviceName,
22
+ enabled: true,
23
+ metrics: {
24
+ flowStarted: Metric.increment(metrics.flowStartedTotal),
25
+ flowCompleted: Metric.increment(metrics.flowCompletedTotal),
26
+ flowFailed: Metric.increment(metrics.flowFailedTotal),
27
+ nodeExecuted: Metric.increment(metrics.nodeExecutedTotal),
28
+ },
29
+ });
30
+ };
31
+
32
+ /**
33
+ * Default live flow observability layer
34
+ */
35
+ export const FlowObservabilityLive = makeFlowObservabilityLive();
36
+
37
+ /**
38
+ * No-op flow observability layer (for testing or disabled observability)
39
+ */
40
+ export const FlowObservabilityDisabled = makeFlowObservabilityLayer(false);
41
+
42
+ /**
43
+ * Helper to get flow metrics from context
44
+ */
45
+ export const getFlowMetrics = Effect.gen(function* () {
46
+ const obs = yield* FlowObservability;
47
+ return obs.metrics;
48
+ });
49
+
50
+ /**
51
+ * Helper to track flow duration
52
+ */
53
+ export const withFlowDuration = <A, E, R>(
54
+ effect: Effect.Effect<A, E, R>,
55
+ ): Effect.Effect<A, E, R> => {
56
+ const metrics = createFlowMetrics();
57
+ return Effect.gen(function* () {
58
+ const startTime = Date.now();
59
+ const result = yield* effect;
60
+ const duration = (Date.now() - startTime) / 1000; // Convert to seconds
61
+ yield* Metric.update(metrics.flowDurationHistogram, duration);
62
+ yield* Metric.update(metrics.flowLatencySummary, duration);
63
+ return result;
64
+ }).pipe(Effect.withSpan("flow-execution"));
65
+ };
66
+
67
+ /**
68
+ * Helper to track node duration
69
+ */
70
+ export const withNodeDuration = <A, E, R>(
71
+ nodeId: string,
72
+ nodeType: string,
73
+ effect: Effect.Effect<A, E, R>,
74
+ ): Effect.Effect<A, E, R> => {
75
+ const metrics = createFlowMetrics();
76
+ return Effect.gen(function* () {
77
+ const startTime = Date.now();
78
+ const result = yield* effect;
79
+ const duration = (Date.now() - startTime) / 1000; // Convert to seconds
80
+ yield* Metric.update(metrics.nodeDurationHistogram, duration);
81
+ yield* Metric.update(metrics.nodeLatencySummary, duration);
82
+ return result;
83
+ }).pipe(
84
+ Effect.withSpan(`node-${nodeType}`, {
85
+ attributes: {
86
+ "node.id": nodeId,
87
+ "node.type": nodeType,
88
+ },
89
+ }),
90
+ );
91
+ };
92
+
93
+ /**
94
+ * Helper to track active flows
95
+ */
96
+ export const trackActiveFlow = <A, E, R>(
97
+ effect: Effect.Effect<A, E, R>,
98
+ ): Effect.Effect<A, E, R> => {
99
+ const metrics = createFlowMetrics();
100
+ return Effect.gen(function* () {
101
+ // Increment active flows
102
+ yield* Metric.increment(metrics.activeFlowsGauge);
103
+
104
+ // Use acquireUseRelease for proper cleanup
105
+ return yield* Effect.acquireUseRelease(
106
+ Effect.void,
107
+ () => effect,
108
+ () => Metric.set(metrics.activeFlowsGauge, -1),
109
+ );
110
+ });
111
+ };
112
+
113
+ /**
114
+ * Helper to track active nodes
115
+ */
116
+ export const trackActiveNode = <A, E, R>(
117
+ effect: Effect.Effect<A, E, R>,
118
+ ): Effect.Effect<A, E, R> => {
119
+ const metrics = createFlowMetrics();
120
+ return Effect.gen(function* () {
121
+ // Increment active nodes
122
+ yield* Metric.increment(metrics.activeNodesGauge);
123
+
124
+ // Use acquireUseRelease for proper cleanup
125
+ return yield* Effect.acquireUseRelease(
126
+ Effect.void,
127
+ () => effect,
128
+ () => Metric.set(metrics.activeNodesGauge, -1),
129
+ );
130
+ });
131
+ };
@@ -0,0 +1,130 @@
1
+ import { Metric, MetricBoundaries } from "effect";
2
+
3
+ // ============================================================================
4
+ // Flow Engine Metrics
5
+ // ============================================================================
6
+
7
+ /**
8
+ * Flow engine metrics for tracking flow execution operations
9
+ */
10
+ export const createFlowMetrics = () => ({
11
+ // Counter metrics
12
+ flowStartedTotal: Metric.counter("flow_started_total", {
13
+ description: "Total number of flows started",
14
+ }),
15
+
16
+ flowCompletedTotal: Metric.counter("flow_completed_total", {
17
+ description: "Total number of flows completed successfully",
18
+ }),
19
+
20
+ flowFailedTotal: Metric.counter("flow_failed_total", {
21
+ description: "Total number of flows that failed",
22
+ }),
23
+
24
+ flowPausedTotal: Metric.counter("flow_paused_total", {
25
+ description: "Total number of flows that were paused",
26
+ }),
27
+
28
+ flowResumedTotal: Metric.counter("flow_resumed_total", {
29
+ description: "Total number of flows that were resumed",
30
+ }),
31
+
32
+ nodeExecutedTotal: Metric.counter("node_executed_total", {
33
+ description: "Total number of nodes executed",
34
+ }),
35
+
36
+ nodeSuccessTotal: Metric.counter("node_success_total", {
37
+ description: "Total number of nodes executed successfully",
38
+ }),
39
+
40
+ nodeFailedTotal: Metric.counter("node_failed_total", {
41
+ description: "Total number of nodes that failed",
42
+ }),
43
+
44
+ nodeSkippedTotal: Metric.counter("node_skipped_total", {
45
+ description: "Total number of nodes skipped (conditional)",
46
+ }),
47
+
48
+ // Histogram metrics
49
+ flowDurationHistogram: Metric.histogram(
50
+ "flow_duration_seconds",
51
+ MetricBoundaries.exponential({
52
+ start: 0.1, // 100ms
53
+ factor: 2,
54
+ count: 20, // Up to ~100 seconds
55
+ }),
56
+ "Duration of complete flow execution in seconds",
57
+ ),
58
+
59
+ nodeDurationHistogram: Metric.histogram(
60
+ "node_duration_seconds",
61
+ MetricBoundaries.exponential({
62
+ start: 0.01, // 10ms
63
+ factor: 2,
64
+ count: 18, // Up to ~26 seconds
65
+ }),
66
+ "Duration of individual node execution in seconds",
67
+ ),
68
+
69
+ flowNodeCountHistogram: Metric.histogram(
70
+ "flow_node_count",
71
+ MetricBoundaries.linear({
72
+ start: 1,
73
+ width: 5,
74
+ count: 20, // Up to 100 nodes
75
+ }),
76
+ "Number of nodes in a flow",
77
+ ),
78
+
79
+ parallelNodesHistogram: Metric.histogram(
80
+ "parallel_nodes_count",
81
+ MetricBoundaries.linear({
82
+ start: 1,
83
+ width: 2,
84
+ count: 15, // Up to 30 parallel nodes
85
+ }),
86
+ "Number of nodes executed in parallel",
87
+ ),
88
+
89
+ // Gauge metrics
90
+ activeFlowsGauge: Metric.gauge("active_flows", {
91
+ description: "Number of currently active flows",
92
+ }),
93
+
94
+ activeNodesGauge: Metric.gauge("active_nodes", {
95
+ description: "Number of currently executing nodes",
96
+ }),
97
+
98
+ pausedFlowsGauge: Metric.gauge("paused_flows", {
99
+ description: "Number of currently paused flows",
100
+ }),
101
+
102
+ // Summary metrics for latency percentiles
103
+ flowLatencySummary: Metric.summary({
104
+ name: "flow_latency_seconds",
105
+ maxAge: "10 minutes",
106
+ maxSize: 1000,
107
+ error: 0.01,
108
+ quantiles: [0.5, 0.9, 0.95, 0.99],
109
+ description: "Flow execution latency percentiles",
110
+ }),
111
+
112
+ nodeLatencySummary: Metric.summary({
113
+ name: "node_latency_seconds",
114
+ maxAge: "10 minutes",
115
+ maxSize: 1000,
116
+ error: 0.01,
117
+ quantiles: [0.5, 0.9, 0.95, 0.99],
118
+ description: "Node execution latency percentiles",
119
+ }),
120
+ });
121
+
122
+ /**
123
+ * Type for flow metrics
124
+ */
125
+ export type FlowMetrics = ReturnType<typeof createFlowMetrics>;
126
+
127
+ /**
128
+ * Default flow metrics instance
129
+ */
130
+ export const flowMetrics = createFlowMetrics();
@@ -0,0 +1,33 @@
1
+ import { Effect, Layer } from "effect";
2
+ import { FlowObservability } from "../core/layers.js";
3
+ import type { FlowObservabilityService } from "../core/layers.js";
4
+
5
+ // ============================================================================
6
+ // Test Flow Observability Layers
7
+ // ============================================================================
8
+
9
+ /**
10
+ * Mock flow observability for testing
11
+ */
12
+ export const makeTestFlowObservability = (): Layer.Layer<FlowObservability> => {
13
+ const service: FlowObservabilityService = {
14
+ serviceName: "test-flow-engine",
15
+ enabled: true,
16
+ metrics: {
17
+ flowStarted: Effect.void,
18
+ flowCompleted: Effect.void,
19
+ flowFailed: Effect.void,
20
+ nodeExecuted: Effect.void,
21
+ },
22
+ };
23
+ return Layer.succeed(FlowObservability, service);
24
+ };
25
+
26
+ /**
27
+ * Run an effect with test flow observability
28
+ */
29
+ export const runWithTestFlowObservability = <A, E>(
30
+ effect: Effect.Effect<A, E, FlowObservability>,
31
+ ): Effect.Effect<A, E> => {
32
+ return effect.pipe(Effect.provide(makeTestFlowObservability()));
33
+ };
@@ -0,0 +1,72 @@
1
+ import { Effect } from "effect";
2
+
3
+ // ============================================================================
4
+ // Flow Tracing Utilities
5
+ // ============================================================================
6
+
7
+ /**
8
+ * Wrap an Effect with a flow operation span
9
+ */
10
+ export const withFlowSpan =
11
+ <A, E, R>(operation: string, attributes?: Record<string, unknown>) =>
12
+ (effect: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>
13
+ effect.pipe(
14
+ Effect.withSpan(`flow-${operation}`, {
15
+ attributes: {
16
+ "flow.operation": operation,
17
+ ...attributes,
18
+ },
19
+ }),
20
+ );
21
+
22
+ /**
23
+ * Add flow context to the current span
24
+ */
25
+ export const withFlowContext = (context: {
26
+ flowId?: string;
27
+ flowName?: string;
28
+ jobId?: string;
29
+ nodeCount?: number;
30
+ storageId?: string;
31
+ }) =>
32
+ Effect.annotateCurrentSpan({
33
+ "flow.id": context.flowId ?? "unknown",
34
+ "flow.name": context.flowName ?? "unknown",
35
+ "flow.job_id": context.jobId ?? "unknown",
36
+ "flow.node_count": context.nodeCount?.toString() ?? "0",
37
+ "flow.storage_id": context.storageId ?? "unknown",
38
+ });
39
+
40
+ /**
41
+ * Add node context to the current span
42
+ */
43
+ export const withNodeContext = (context: {
44
+ nodeId: string;
45
+ nodeType: string;
46
+ nodeName?: string;
47
+ flowId?: string;
48
+ jobId?: string;
49
+ }) =>
50
+ Effect.annotateCurrentSpan({
51
+ "node.id": context.nodeId,
52
+ "node.type": context.nodeType,
53
+ "node.name": context.nodeName ?? "unknown",
54
+ "node.flow_id": context.flowId ?? "unknown",
55
+ "node.job_id": context.jobId ?? "unknown",
56
+ });
57
+
58
+ /**
59
+ * Add execution state context to the current span
60
+ */
61
+ export const withExecutionContext = (context: {
62
+ executionOrder?: string[];
63
+ currentIndex?: number;
64
+ totalNodes?: number;
65
+ parallelCount?: number;
66
+ }) =>
67
+ Effect.annotateCurrentSpan({
68
+ "execution.order": context.executionOrder?.join(",") ?? "",
69
+ "execution.current_index": context.currentIndex?.toString() ?? "0",
70
+ "execution.total_nodes": context.totalNodes?.toString() ?? "0",
71
+ "execution.parallel_count": context.parallelCount?.toString() ?? "0",
72
+ });
package/src/index.ts ADDED
@@ -0,0 +1,31 @@
1
+ // Main observability package exports
2
+ export * from "./core/index.js";
3
+ // Tracing specific exports
4
+ export { NodeSdkLive, WebSdkLive, WorkersSdkLive } from "./core/tracing.js";
5
+ export * from "./flow/index.js";
6
+ export * from "./service/metrics.js";
7
+ export * from "./storage/index.js";
8
+ // Convenience re-exports for common use cases
9
+ export {
10
+ logS3Context,
11
+ logS3Operation,
12
+ logS3UploadCompletion,
13
+ logS3UploadProgress,
14
+ S3ObservabilityLayer,
15
+ S3TracingLayer,
16
+ s3ActiveUploadsGauge,
17
+ s3ApiCallsTotal,
18
+ s3FileSizeHistogram,
19
+ // S3 specific
20
+ s3Metrics,
21
+ s3UploadDurationHistogram,
22
+ // Individual S3 metrics
23
+ s3UploadRequestsTotal,
24
+ trackS3Error,
25
+ withS3ApiMetrics,
26
+ withS3OperationMetrics,
27
+ withS3Span,
28
+ withS3TimingMetrics,
29
+ withS3UploadMetrics,
30
+ } from "./storage/s3.js";
31
+ export * from "./upload/index.js";
@@ -0,0 +1,31 @@
1
+ import { Context, Effect, Layer } from "effect";
2
+
3
+ /**
4
+ * Metrics Recording Service
5
+ *
6
+ * Provides access to metrics recording functionality throughout
7
+ * the upload and flow processing pipeline. The service is provided
8
+ * via Effect Layer and can be accessed using Effect.service().
9
+ */
10
+ export class MetricsService extends Context.Tag("MetricsService")<
11
+ MetricsService,
12
+ {
13
+ /**
14
+ * Record upload metrics for an organization
15
+ */
16
+ readonly recordUpload: (
17
+ clientId: string,
18
+ bytes: number,
19
+ metadata?: Record<string, unknown>,
20
+ ) => Effect.Effect<void, never>;
21
+ }
22
+ >() {}
23
+
24
+ /**
25
+ * No-op implementation of MetricsService that does nothing.
26
+ * Used when metrics are disabled or database is not available.
27
+ */
28
+ export const NoOpMetricsServiceLive: Layer.Layer<MetricsService> =
29
+ Layer.succeed(MetricsService, {
30
+ recordUpload: (_organizationId: string, _bytes: number) => Effect.void,
31
+ });
@@ -0,0 +1,163 @@
1
+ import { type Effect, Layer } from "effect";
2
+ import {
3
+ createStorageErrorTracker,
4
+ type StorageErrorCategory,
5
+ } from "../core/errors.js";
6
+ import {
7
+ logStorageOperation,
8
+ logUploadCompletion,
9
+ logUploadProgress,
10
+ logWithContext,
11
+ } from "../core/logging.js";
12
+ import { createStorageMetrics, type StorageMetrics } from "../core/metrics.js";
13
+ import { createStorageTracingLayer, withStorageSpan } from "../core/tracing.js";
14
+ import {
15
+ withApiMetrics,
16
+ withStorageOperationMetrics,
17
+ withTimingMetrics,
18
+ withUploadMetrics,
19
+ } from "../core/utilities.js";
20
+
21
+ // ============================================================================
22
+ // Azure Blob Storage-Specific Observability
23
+ // ============================================================================
24
+
25
+ const STORAGE_TYPE = "azure";
26
+
27
+ // Azure-specific metrics
28
+ export const azureMetrics = createStorageMetrics(STORAGE_TYPE);
29
+
30
+ // Azure-specific tracing layer
31
+ export const AzureTracingLayer = createStorageTracingLayer(STORAGE_TYPE);
32
+
33
+ // Azure-specific error classification
34
+ const classifyAzureError = (error: unknown): StorageErrorCategory | null => {
35
+ if (!error || typeof error !== "object") return null;
36
+
37
+ const errorCode =
38
+ "code" in error
39
+ ? error.code
40
+ : "statusCode" in error
41
+ ? error.statusCode
42
+ : undefined;
43
+ if (!errorCode) return null;
44
+
45
+ // Azure-specific error codes
46
+ switch (errorCode) {
47
+ case "BlobNotFound":
48
+ case "ContainerNotFound":
49
+ case "InvalidBlobOrBlock":
50
+ return "client_error";
51
+ case "ContainerAlreadyExists":
52
+ case "BlobAlreadyExists":
53
+ return "client_error";
54
+ case "InvalidBlockId":
55
+ case "InvalidBlockList":
56
+ case "InvalidBlobType":
57
+ return "client_error";
58
+ case "RequestBodyTooLarge":
59
+ case "InvalidHeaderValue":
60
+ return "client_error";
61
+ case "AuthenticationFailed":
62
+ case "InvalidAuthenticationInfo":
63
+ return "authentication_error";
64
+ case "AccountIsDisabled":
65
+ return "authorization_error";
66
+ case "InsufficientAccountPermissions":
67
+ return "authorization_error";
68
+ case "OperationTimedOut":
69
+ case "ServerBusy":
70
+ case "InternalError":
71
+ return "server_error";
72
+ default:
73
+ // Check for HTTP status codes
74
+ if (typeof errorCode === "number") {
75
+ if (errorCode >= 500) return "server_error";
76
+ if (errorCode === 429) return "throttling_error";
77
+ if (errorCode === 403) return "authorization_error";
78
+ if (errorCode === 401) return "authentication_error";
79
+ if (errorCode >= 400) return "client_error";
80
+ }
81
+ return null; // Fall back to generic classification
82
+ }
83
+ };
84
+
85
+ // Azure-specific error tracker
86
+ export const trackAzureError = createStorageErrorTracker(
87
+ STORAGE_TYPE,
88
+ azureMetrics,
89
+ classifyAzureError,
90
+ );
91
+
92
+ // Azure-specific observability layer
93
+ export const AzureObservabilityLayer = Layer.mergeAll(
94
+ AzureTracingLayer,
95
+ // Metrics are automatically available through Effect
96
+ );
97
+
98
+ // ============================================================================
99
+ // Azure Utility Functions
100
+ // ============================================================================
101
+
102
+ export const withAzureUploadMetrics = <A, E, R>(
103
+ uploadId: string,
104
+ effect: Effect.Effect<A, E, R>,
105
+ ) => withUploadMetrics(azureMetrics, uploadId, effect);
106
+
107
+ export const withAzureApiMetrics = <A, E, R>(
108
+ operation: string,
109
+ effect: Effect.Effect<A, E, R>,
110
+ ) => withApiMetrics(azureMetrics, operation, effect);
111
+
112
+ export const withAzureTimingMetrics = withTimingMetrics;
113
+
114
+ export const withAzureOperationMetrics = <A, E, R>(
115
+ operation: string,
116
+ uploadId: string,
117
+ effect: Effect.Effect<A, E, R>,
118
+ fileSize?: number,
119
+ ) =>
120
+ withStorageOperationMetrics(
121
+ azureMetrics,
122
+ operation,
123
+ uploadId,
124
+ effect,
125
+ fileSize,
126
+ );
127
+
128
+ // Azure-specific span wrapper
129
+ export const withAzureSpan =
130
+ <A, E, R>(operation: string, attributes?: Record<string, unknown>) =>
131
+ (effect: Effect.Effect<A, E, R>) =>
132
+ withStorageSpan(operation, STORAGE_TYPE, attributes)(effect);
133
+
134
+ // Azure-specific logging functions
135
+ export const logAzureOperation = logStorageOperation.bind(null, STORAGE_TYPE);
136
+ export const logAzureUploadProgress = logUploadProgress.bind(
137
+ null,
138
+ STORAGE_TYPE,
139
+ );
140
+ export const logAzureUploadCompletion = logUploadCompletion.bind(
141
+ null,
142
+ STORAGE_TYPE,
143
+ );
144
+ export const logAzureContext = logWithContext;
145
+
146
+ // Export metrics for external access
147
+ export const {
148
+ uploadRequestsTotal: azureUploadRequestsTotal,
149
+ uploadPartsTotal: azureUploadPartsTotal,
150
+ uploadSuccessTotal: azureUploadSuccessTotal,
151
+ uploadErrorsTotal: azureUploadErrorsTotal,
152
+ apiCallsTotal: azureApiCallsTotal,
153
+ uploadDurationHistogram: azureUploadDurationHistogram,
154
+ partUploadDurationHistogram: azurePartUploadDurationHistogram,
155
+ fileSizeHistogram: azureFileSizeHistogram,
156
+ partSizeHistogram: azurePartSizeHistogram,
157
+ activeUploadsGauge: azureActiveUploadsGauge,
158
+ uploadThroughputGauge: azureUploadThroughputGauge,
159
+ uploadLatencySummary: azureUploadLatencySummary,
160
+ } = azureMetrics;
161
+
162
+ // Type exports
163
+ export type AzureMetrics = StorageMetrics;