@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,153 @@
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
+ // Filesystem Storage-Specific Observability
23
+ // ============================================================================
24
+
25
+ const STORAGE_TYPE = "filesystem";
26
+
27
+ // Filesystem-specific metrics
28
+ export const filesystemMetrics = createStorageMetrics(STORAGE_TYPE);
29
+
30
+ // Filesystem-specific tracing layer
31
+ export const FilesystemTracingLayer = createStorageTracingLayer(STORAGE_TYPE);
32
+
33
+ // Filesystem-specific error classification
34
+ const classifyFilesystemError = (
35
+ error: unknown,
36
+ ): StorageErrorCategory | null => {
37
+ if (!error || typeof error !== "object") return null;
38
+
39
+ const errorCode = "code" in error ? error.code : undefined;
40
+ if (!errorCode) return null;
41
+
42
+ // Node.js filesystem error codes
43
+ switch (errorCode) {
44
+ case "ENOENT": // File/directory not found
45
+ case "ENOTDIR": // Not a directory
46
+ return "client_error";
47
+ case "EEXIST": // File/directory already exists
48
+ return "client_error";
49
+ case "EISDIR": // Is a directory
50
+ return "client_error";
51
+ case "EINVAL": // Invalid argument
52
+ case "ENAMETOOLONG": // Filename too long
53
+ return "client_error";
54
+ case "EACCES": // Permission denied
55
+ case "EPERM": // Operation not permitted
56
+ return "authorization_error";
57
+ case "ENOSPC": // No space left on device
58
+ case "EDQUOT": // Disk quota exceeded
59
+ return "server_error";
60
+ case "EIO": // I/O error
61
+ case "EROFS": // Read-only filesystem
62
+ case "EMFILE": // Too many open files
63
+ case "ENFILE": // File table overflow
64
+ return "server_error";
65
+ case "EBUSY": // Device or resource busy
66
+ return "throttling_error";
67
+ default:
68
+ return null; // Fall back to generic classification
69
+ }
70
+ };
71
+
72
+ // Filesystem-specific error tracker
73
+ export const trackFilesystemError = createStorageErrorTracker(
74
+ STORAGE_TYPE,
75
+ filesystemMetrics,
76
+ classifyFilesystemError,
77
+ );
78
+
79
+ // Filesystem-specific observability layer
80
+ export const FilesystemObservabilityLayer = Layer.mergeAll(
81
+ FilesystemTracingLayer,
82
+ // Metrics are automatically available through Effect
83
+ );
84
+
85
+ // ============================================================================
86
+ // Filesystem Utility Functions
87
+ // ============================================================================
88
+
89
+ export const withFilesystemUploadMetrics = <A, E, R>(
90
+ uploadId: string,
91
+ effect: Effect.Effect<A, E, R>,
92
+ ) => withUploadMetrics(filesystemMetrics, uploadId, effect);
93
+
94
+ export const withFilesystemApiMetrics = <A, E, R>(
95
+ operation: string,
96
+ effect: Effect.Effect<A, E, R>,
97
+ ) => withApiMetrics(filesystemMetrics, operation, effect);
98
+
99
+ export const withFilesystemTimingMetrics = withTimingMetrics;
100
+
101
+ export const withFilesystemOperationMetrics = <A, E, R>(
102
+ operation: string,
103
+ uploadId: string,
104
+ effect: Effect.Effect<A, E, R>,
105
+ fileSize?: number,
106
+ ) =>
107
+ withStorageOperationMetrics(
108
+ filesystemMetrics,
109
+ operation,
110
+ uploadId,
111
+ effect,
112
+ fileSize,
113
+ );
114
+
115
+ // Filesystem-specific span wrapper
116
+ export const withFilesystemSpan =
117
+ <A, E, R>(operation: string, attributes?: Record<string, unknown>) =>
118
+ (effect: Effect.Effect<A, E, R>) =>
119
+ withStorageSpan(operation, STORAGE_TYPE, attributes)(effect);
120
+
121
+ // Filesystem-specific logging functions
122
+ export const logFilesystemOperation = logStorageOperation.bind(
123
+ null,
124
+ STORAGE_TYPE,
125
+ );
126
+ export const logFilesystemUploadProgress = logUploadProgress.bind(
127
+ null,
128
+ STORAGE_TYPE,
129
+ );
130
+ export const logFilesystemUploadCompletion = logUploadCompletion.bind(
131
+ null,
132
+ STORAGE_TYPE,
133
+ );
134
+ export const logFilesystemContext = logWithContext;
135
+
136
+ // Export metrics for external access
137
+ export const {
138
+ uploadRequestsTotal: filesystemUploadRequestsTotal,
139
+ uploadPartsTotal: filesystemUploadPartsTotal,
140
+ uploadSuccessTotal: filesystemUploadSuccessTotal,
141
+ uploadErrorsTotal: filesystemUploadErrorsTotal,
142
+ apiCallsTotal: filesystemApiCallsTotal,
143
+ uploadDurationHistogram: filesystemUploadDurationHistogram,
144
+ partUploadDurationHistogram: filesystemPartUploadDurationHistogram,
145
+ fileSizeHistogram: filesystemFileSizeHistogram,
146
+ partSizeHistogram: filesystemPartSizeHistogram,
147
+ activeUploadsGauge: filesystemActiveUploadsGauge,
148
+ uploadThroughputGauge: filesystemUploadThroughputGauge,
149
+ uploadLatencySummary: filesystemUploadLatencySummary,
150
+ } = filesystemMetrics;
151
+
152
+ // Type exports
153
+ export type FilesystemMetrics = StorageMetrics;
@@ -0,0 +1,161 @@
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
+ // Google Cloud Storage-Specific Observability
23
+ // ============================================================================
24
+
25
+ const STORAGE_TYPE = "gcs";
26
+
27
+ // GCS-specific metrics
28
+ export const gcsMetrics = createStorageMetrics(STORAGE_TYPE);
29
+
30
+ // GCS-specific tracing layer
31
+ export const GCSTracingLayer = createStorageTracingLayer(STORAGE_TYPE);
32
+
33
+ // GCS-specific error classification
34
+ const classifyGCSError = (error: unknown): StorageErrorCategory | null => {
35
+ if (!error || typeof error !== "object") return null;
36
+
37
+ const errorCode =
38
+ "code" in error ? error.code : "status" in error ? error.status : undefined;
39
+ if (!errorCode) return null;
40
+
41
+ // GCS-specific error codes
42
+ switch (errorCode) {
43
+ case "NoSuchBucket":
44
+ case "NoSuchKey":
45
+ case "NoSuchUpload":
46
+ return "client_error";
47
+ case "BucketAlreadyOwnedByYou":
48
+ case "BucketNotEmpty":
49
+ return "client_error";
50
+ case "InvalidBucketName":
51
+ case "InvalidArgument":
52
+ case "InvalidPart":
53
+ case "InvalidPartOrder":
54
+ return "client_error";
55
+ case "EntityTooSmall":
56
+ case "EntityTooLarge":
57
+ return "client_error";
58
+ case "MalformedPolicy":
59
+ return "client_error";
60
+ case "Unauthorized":
61
+ case "AuthenticationRequired":
62
+ return "authentication_error";
63
+ case "Forbidden":
64
+ case "AccessDenied":
65
+ return "authorization_error";
66
+ case "TooManyRequests":
67
+ case "RateLimitExceeded":
68
+ return "throttling_error";
69
+ case "InternalError":
70
+ case "ServiceUnavailable":
71
+ case "BackendError":
72
+ return "server_error";
73
+ default:
74
+ // Check for HTTP status codes
75
+ if (typeof errorCode === "number") {
76
+ if (errorCode >= 500) return "server_error";
77
+ if (errorCode === 429) return "throttling_error";
78
+ if (errorCode === 403) return "authorization_error";
79
+ if (errorCode === 401) return "authentication_error";
80
+ if (errorCode >= 400) return "client_error";
81
+ }
82
+ return null; // Fall back to generic classification
83
+ }
84
+ };
85
+
86
+ // GCS-specific error tracker
87
+ export const trackGCSError = createStorageErrorTracker(
88
+ STORAGE_TYPE,
89
+ gcsMetrics,
90
+ classifyGCSError,
91
+ );
92
+
93
+ // GCS-specific observability layer
94
+ export const GCSObservabilityLayer = Layer.mergeAll(
95
+ GCSTracingLayer,
96
+ // Metrics are automatically available through Effect
97
+ );
98
+
99
+ // ============================================================================
100
+ // GCS Utility Functions
101
+ // ============================================================================
102
+
103
+ export const withGCSUploadMetrics = <A, E, R>(
104
+ uploadId: string,
105
+ effect: Effect.Effect<A, E, R>,
106
+ ) => withUploadMetrics(gcsMetrics, uploadId, effect);
107
+
108
+ export const withGCSApiMetrics = <A, E, R>(
109
+ operation: string,
110
+ effect: Effect.Effect<A, E, R>,
111
+ ) => withApiMetrics(gcsMetrics, operation, effect);
112
+
113
+ export const withGCSTimingMetrics = withTimingMetrics;
114
+
115
+ export const withGCSOperationMetrics = <A, E, R>(
116
+ operation: string,
117
+ uploadId: string,
118
+ effect: Effect.Effect<A, E, R>,
119
+ fileSize?: number,
120
+ ) =>
121
+ withStorageOperationMetrics(
122
+ gcsMetrics,
123
+ operation,
124
+ uploadId,
125
+ effect,
126
+ fileSize,
127
+ );
128
+
129
+ // GCS-specific span wrapper
130
+ export const withGCSSpan =
131
+ <A, E, R>(operation: string, attributes?: Record<string, unknown>) =>
132
+ (effect: Effect.Effect<A, E, R>) =>
133
+ withStorageSpan(operation, STORAGE_TYPE, attributes)(effect);
134
+
135
+ // GCS-specific logging functions
136
+ export const logGCSOperation = logStorageOperation.bind(null, STORAGE_TYPE);
137
+ export const logGCSUploadProgress = logUploadProgress.bind(null, STORAGE_TYPE);
138
+ export const logGCSUploadCompletion = logUploadCompletion.bind(
139
+ null,
140
+ STORAGE_TYPE,
141
+ );
142
+ export const logGCSContext = logWithContext;
143
+
144
+ // Export metrics for external access
145
+ export const {
146
+ uploadRequestsTotal: gcsUploadRequestsTotal,
147
+ uploadPartsTotal: gcsUploadPartsTotal,
148
+ uploadSuccessTotal: gcsUploadSuccessTotal,
149
+ uploadErrorsTotal: gcsUploadErrorsTotal,
150
+ apiCallsTotal: gcsApiCallsTotal,
151
+ uploadDurationHistogram: gcsUploadDurationHistogram,
152
+ partUploadDurationHistogram: gcsPartUploadDurationHistogram,
153
+ fileSizeHistogram: gcsFileSizeHistogram,
154
+ partSizeHistogram: gcsPartSizeHistogram,
155
+ activeUploadsGauge: gcsActiveUploadsGauge,
156
+ uploadThroughputGauge: gcsUploadThroughputGauge,
157
+ uploadLatencySummary: gcsUploadLatencySummary,
158
+ } = gcsMetrics;
159
+
160
+ // Type exports
161
+ export type GCSMetrics = StorageMetrics;
@@ -0,0 +1,5 @@
1
+ // Storage-specific observability exports
2
+ export * from "./s3.js";
3
+ export * from "./azure.js";
4
+ export * from "./gcs.js";
5
+ export * from "./filesystem.js";
@@ -0,0 +1,136 @@
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
+ // S3-Specific Observability
23
+ // ============================================================================
24
+
25
+ const STORAGE_TYPE = "s3";
26
+
27
+ // S3-specific metrics
28
+ export const s3Metrics = createStorageMetrics(STORAGE_TYPE);
29
+
30
+ // S3-specific tracing layer
31
+ export const S3TracingLayer = createStorageTracingLayer(STORAGE_TYPE);
32
+
33
+ // S3-specific error classification
34
+ const classifyS3Error = (error: unknown): StorageErrorCategory | null => {
35
+ if (!error || typeof error !== "object") return null;
36
+
37
+ const errorCode = "code" in error ? error.code : undefined;
38
+ if (!errorCode) return null;
39
+
40
+ // S3-specific error codes
41
+ switch (errorCode) {
42
+ case "NoSuchKey":
43
+ case "NoSuchBucket":
44
+ case "NoSuchUpload":
45
+ return "client_error";
46
+ case "BucketAlreadyExists":
47
+ case "BucketNotEmpty":
48
+ return "client_error";
49
+ case "InvalidBucketName":
50
+ case "InvalidPart":
51
+ case "InvalidPartOrder":
52
+ return "client_error";
53
+ case "EntityTooSmall":
54
+ case "EntityTooLarge":
55
+ return "client_error";
56
+ case "ExpiredToken":
57
+ case "TokenRefreshRequired":
58
+ return "authentication_error";
59
+ case "RequestTimeTooSkewed":
60
+ case "SlowDown":
61
+ return "throttling_error";
62
+ default:
63
+ return null; // Fall back to generic classification
64
+ }
65
+ };
66
+
67
+ // S3-specific error tracker
68
+ export const trackS3Error = createStorageErrorTracker(
69
+ STORAGE_TYPE,
70
+ s3Metrics,
71
+ classifyS3Error,
72
+ );
73
+
74
+ // S3-specific observability layer
75
+ export const S3ObservabilityLayer = Layer.mergeAll(
76
+ S3TracingLayer,
77
+ // Metrics are automatically available through Effect
78
+ );
79
+
80
+ // ============================================================================
81
+ // S3 Utility Functions
82
+ // ============================================================================
83
+
84
+ export const withS3UploadMetrics = <A, E, R>(
85
+ uploadId: string,
86
+ effect: Effect.Effect<A, E, R>,
87
+ ) => withUploadMetrics(s3Metrics, uploadId, effect);
88
+
89
+ export const withS3ApiMetrics = <A, E, R>(
90
+ operation: string,
91
+ effect: Effect.Effect<A, E, R>,
92
+ ) => withApiMetrics(s3Metrics, operation, effect);
93
+
94
+ export const withS3TimingMetrics = withTimingMetrics;
95
+
96
+ export const withS3OperationMetrics = <A, E, R>(
97
+ operation: string,
98
+ uploadId: string,
99
+ effect: Effect.Effect<A, E, R>,
100
+ fileSize?: number,
101
+ ) =>
102
+ withStorageOperationMetrics(s3Metrics, operation, uploadId, effect, fileSize);
103
+
104
+ // S3-specific span wrapper
105
+ export const withS3Span =
106
+ <A, E, R>(operation: string, attributes?: Record<string, unknown>) =>
107
+ (effect: Effect.Effect<A, E, R>) =>
108
+ withStorageSpan(operation, STORAGE_TYPE, attributes)(effect);
109
+
110
+ // S3-specific logging functions
111
+ export const logS3Operation = logStorageOperation.bind(null, STORAGE_TYPE);
112
+ export const logS3UploadProgress = logUploadProgress.bind(null, STORAGE_TYPE);
113
+ export const logS3UploadCompletion = logUploadCompletion.bind(
114
+ null,
115
+ STORAGE_TYPE,
116
+ );
117
+ export const logS3Context = logWithContext;
118
+
119
+ // Export metrics for external access
120
+ export const {
121
+ uploadRequestsTotal: s3UploadRequestsTotal,
122
+ uploadPartsTotal: s3UploadPartsTotal,
123
+ uploadSuccessTotal: s3UploadSuccessTotal,
124
+ uploadErrorsTotal: s3UploadErrorsTotal,
125
+ apiCallsTotal: s3ApiCallsTotal,
126
+ uploadDurationHistogram: s3UploadDurationHistogram,
127
+ partUploadDurationHistogram: s3PartUploadDurationHistogram,
128
+ fileSizeHistogram: s3FileSizeHistogram,
129
+ partSizeHistogram: s3PartSizeHistogram,
130
+ activeUploadsGauge: s3ActiveUploadsGauge,
131
+ uploadThroughputGauge: s3UploadThroughputGauge,
132
+ uploadLatencySummary: s3UploadLatencySummary,
133
+ } = s3Metrics;
134
+
135
+ // Type exports
136
+ export type S3Metrics = StorageMetrics;
@@ -0,0 +1,234 @@
1
+ /**
2
+ * Validation test for core observability infrastructure
3
+ *
4
+ * This test validates:
5
+ * 1. Layer creation and composition
6
+ * 2. Storage observability with metrics
7
+ * 3. Error classification
8
+ * 4. Tracing with spans
9
+ * 5. Structured logging
10
+ */
11
+
12
+ import { Effect, Layer, Metric } from "effect";
13
+ import {
14
+ classifyStorageError,
15
+ createStorageMetrics,
16
+ makeStorageObservabilityLayer,
17
+ StorageObservability,
18
+ trackStorageError,
19
+ withStorageSpan,
20
+ logStorageOperation,
21
+ logUploadCompletion,
22
+ } from "./index.js";
23
+
24
+ // ============================================================================
25
+ // Test: Create Storage Observability Layer
26
+ // ============================================================================
27
+
28
+ const testStorageType = "test-s3";
29
+ const testMetrics = createStorageMetrics(testStorageType);
30
+ const TestStorageObservabilityLayer = makeStorageObservabilityLayer(
31
+ testStorageType,
32
+ testMetrics,
33
+ true,
34
+ );
35
+
36
+ // ============================================================================
37
+ // Test: Simulate Upload Operation with Full Observability
38
+ // ============================================================================
39
+
40
+ const simulateUpload = (fileSize: number, shouldFail: boolean) =>
41
+ Effect.gen(function* () {
42
+ const obs = yield* StorageObservability;
43
+
44
+ console.log(`\n๐Ÿ“Š Testing with storage type: ${obs.storageType}`);
45
+ console.log(`Service: ${obs.serviceName}`);
46
+ console.log(`Enabled: ${obs.enabled}`);
47
+
48
+ // Log operation start
49
+ yield* logStorageOperation(
50
+ testStorageType,
51
+ "uploadFile",
52
+ "test-upload-123",
53
+ { file_size: fileSize }
54
+ );
55
+
56
+ // Simulate upload with tracing
57
+ const uploadEffect = Effect.gen(function* () {
58
+ // Track upload request
59
+ yield* obs.metrics.uploadRequestsTotal.pipe(
60
+ Metric.tagged("upload_id", "test-upload-123")
61
+ )(Effect.succeed(1));
62
+
63
+ // Track file size
64
+ yield* obs.metrics.fileSizeHistogram(Effect.succeed(fileSize));
65
+
66
+ // Simulate upload work
67
+ yield* Effect.sleep("100 millis");
68
+
69
+ if (shouldFail) {
70
+ return yield* Effect.fail(new Error("NetworkError: Connection timeout"));
71
+ }
72
+
73
+ // Track success
74
+ yield* obs.metrics.uploadSuccessTotal.pipe(
75
+ Metric.tagged("upload_id", "test-upload-123")
76
+ )(Effect.succeed(1));
77
+
78
+ return { uploadId: "test-upload-123", key: "test-file.jpg" };
79
+ });
80
+
81
+ // Wrap with span
82
+ const result = yield* uploadEffect.pipe(
83
+ withStorageSpan("uploadFile", testStorageType, {
84
+ "file.size": fileSize,
85
+ "upload.id": "test-upload-123",
86
+ })
87
+ );
88
+
89
+ // Log completion
90
+ yield* logUploadCompletion(testStorageType, "test-upload-123", {
91
+ fileSize,
92
+ totalDurationMs: 100,
93
+ throughputBps: (fileSize / 100) * 1000,
94
+ });
95
+
96
+ return result;
97
+ });
98
+
99
+ // ============================================================================
100
+ // Test: Error Classification
101
+ // ============================================================================
102
+
103
+ const testErrorClassification = Effect.gen(function* () {
104
+ console.log("\n๐Ÿ” Testing Error Classification:");
105
+
106
+ const testErrors = [
107
+ { error: { code: "NetworkError" }, expected: "network_error" },
108
+ { error: { code: "InvalidAccessKeyId" }, expected: "authentication_error" },
109
+ { error: { code: "AccessDenied" }, expected: "authorization_error" },
110
+ { error: { code: "SlowDown" }, expected: "throttling_error" },
111
+ { error: { code: "InternalError" }, expected: "server_error" },
112
+ { error: { code: "InvalidRequest" }, expected: "client_error" },
113
+ { error: { code: "UnknownError" }, expected: "unknown_error" },
114
+ ];
115
+
116
+ for (const { error, expected } of testErrors) {
117
+ const category = classifyStorageError(error);
118
+ const status = category === expected ? "โœ…" : "โŒ";
119
+ console.log(` ${status} ${error.code} -> ${category} (expected: ${expected})`);
120
+ }
121
+ });
122
+
123
+ // ============================================================================
124
+ // Test: Error Tracking with Metrics
125
+ // ============================================================================
126
+
127
+ const testErrorTracking = Effect.gen(function* () {
128
+ const obs = yield* StorageObservability;
129
+
130
+ console.log("\nโš ๏ธ Testing Error Tracking:");
131
+
132
+ const error = new Error("ECONNRESET: Connection reset by peer");
133
+ (error as any).code = "ECONNRESET";
134
+
135
+ yield* trackStorageError(
136
+ testStorageType,
137
+ obs.metrics,
138
+ "uploadPart",
139
+ error,
140
+ { upload_id: "test-upload-456", part_number: 1 }
141
+ );
142
+
143
+ console.log(" โœ… Error tracked with metrics and logs");
144
+ });
145
+
146
+ // ============================================================================
147
+ // Test: Successful Upload Flow
148
+ // ============================================================================
149
+
150
+ const testSuccessfulUpload = Effect.gen(function* () {
151
+ console.log("\nโœ… Testing Successful Upload:");
152
+
153
+ const result = yield* simulateUpload(1024 * 1024 * 10, false); // 10MB file
154
+ console.log(` โœ… Upload completed: ${result.uploadId} -> ${result.key}`);
155
+ });
156
+
157
+ // ============================================================================
158
+ // Test: Failed Upload Flow
159
+ // ============================================================================
160
+
161
+ const testFailedUpload = Effect.gen(function* () {
162
+ console.log("\nโŒ Testing Failed Upload:");
163
+
164
+ const obs = yield* StorageObservability;
165
+
166
+ // Use either to handle success and failure cases
167
+ const result = yield* Effect.either(simulateUpload(1024 * 1024 * 5, true));
168
+
169
+ if (result._tag === "Left") {
170
+ const error: unknown = result.left;
171
+ const errorMsg = error instanceof Error ? error.message : String(error);
172
+ console.log(` โŒ Upload failed as expected: ${errorMsg}`);
173
+
174
+ // Track the error
175
+ yield* trackStorageError(
176
+ testStorageType,
177
+ obs.metrics,
178
+ "uploadFile",
179
+ error,
180
+ { upload_id: "test-upload-789" }
181
+ );
182
+
183
+ console.log(" โœ… Error handled and tracked successfully");
184
+ } else {
185
+ console.log(" โŒ Upload should have failed but succeeded");
186
+ }
187
+ });
188
+
189
+ // ============================================================================
190
+ // Test: Metrics Snapshot
191
+ // ============================================================================
192
+
193
+ const testMetricsSnapshot = Effect.gen(function* () {
194
+ console.log("\n๐Ÿ“ˆ Testing Metrics Snapshot:");
195
+
196
+ const snapshot = yield* Metric.snapshot;
197
+ console.log(` โœ… Captured ${snapshot.length} metric(s)`);
198
+
199
+ // Display some metrics
200
+ for (const metric of snapshot.slice(0, 5)) {
201
+ console.log(` - ${metric.metricKey.name}: ${JSON.stringify(metric.metricState)}`);
202
+ }
203
+ });
204
+
205
+ // ============================================================================
206
+ // Run All Tests
207
+ // ============================================================================
208
+
209
+ const runTests = Effect.gen(function* () {
210
+ console.log("๐Ÿงช Observability Infrastructure Validation Test");
211
+ console.log("=".repeat(50));
212
+
213
+ // Run all tests
214
+ yield* testErrorClassification;
215
+ yield* testSuccessfulUpload;
216
+ yield* testFailedUpload;
217
+ yield* testErrorTracking;
218
+ yield* testMetricsSnapshot;
219
+
220
+ console.log("\n" + "=".repeat(50));
221
+ console.log("โœ… All tests completed successfully!");
222
+ console.log("\nCore observability infrastructure is working correctly:");
223
+ console.log(" โœ… Layer creation and composition");
224
+ console.log(" โœ… Metrics tracking (counters, histograms, gauges)");
225
+ console.log(" โœ… Error classification and tracking");
226
+ console.log(" โœ… Tracing with spans");
227
+ console.log(" โœ… Structured logging");
228
+ }).pipe(Effect.provide(TestStorageObservabilityLayer));
229
+
230
+ // Run the tests
231
+ Effect.runPromise(runTests).catch((error) => {
232
+ console.error("\nโŒ Test failed:", error);
233
+ process.exit(1);
234
+ });