@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.
- package/.turbo/turbo-build.log +5 -0
- package/.turbo/turbo-check.log +0 -0
- package/LICENSE +21 -0
- package/dist/core/errors.d.ts +8 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +108 -0
- package/dist/core/index.d.ts +8 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +8 -0
- package/dist/core/layers.d.ts +104 -0
- package/dist/core/layers.d.ts.map +1 -0
- package/dist/core/layers.js +110 -0
- package/dist/core/logging.d.ts +18 -0
- package/dist/core/logging.d.ts.map +1 -0
- package/dist/core/logging.js +41 -0
- package/dist/core/metrics.d.ts +37 -0
- package/dist/core/metrics.d.ts.map +1 -0
- package/dist/core/metrics.js +72 -0
- package/dist/core/testing.d.ts +43 -0
- package/dist/core/testing.d.ts.map +1 -0
- package/dist/core/testing.js +93 -0
- package/dist/core/tracing.d.ts +19 -0
- package/dist/core/tracing.d.ts.map +1 -0
- package/dist/core/tracing.js +43 -0
- package/dist/core/utilities.d.ts +11 -0
- package/dist/core/utilities.d.ts.map +1 -0
- package/dist/core/utilities.js +41 -0
- package/dist/flow/errors.d.ts +15 -0
- package/dist/flow/errors.d.ts.map +1 -0
- package/dist/flow/errors.js +66 -0
- package/dist/flow/index.d.ts +6 -0
- package/dist/flow/index.d.ts.map +1 -0
- package/dist/flow/index.js +6 -0
- package/dist/flow/layers.d.ts +40 -0
- package/dist/flow/layers.d.ts.map +1 -0
- package/dist/flow/layers.js +94 -0
- package/dist/flow/metrics.d.ts +52 -0
- package/dist/flow/metrics.d.ts.map +1 -0
- package/dist/flow/metrics.js +89 -0
- package/dist/flow/testing.d.ts +11 -0
- package/dist/flow/testing.d.ts.map +1 -0
- package/dist/flow/testing.js +27 -0
- package/dist/flow/tracing.d.ts +35 -0
- package/dist/flow/tracing.d.ts.map +1 -0
- package/dist/flow/tracing.js +42 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/service/metrics.d.ts +23 -0
- package/dist/service/metrics.d.ts.map +1 -0
- package/dist/service/metrics.js +17 -0
- package/dist/storage/azure.d.ts +47 -0
- package/dist/storage/azure.d.ts.map +1 -0
- package/dist/storage/azure.js +89 -0
- package/dist/storage/filesystem.d.ts +47 -0
- package/dist/storage/filesystem.d.ts.map +1 -0
- package/dist/storage/filesystem.js +70 -0
- package/dist/storage/gcs.d.ts +47 -0
- package/dist/storage/gcs.d.ts.map +1 -0
- package/dist/storage/gcs.js +90 -0
- package/dist/storage/index.d.ts +5 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +5 -0
- package/dist/storage/s3.d.ts +47 -0
- package/dist/storage/s3.d.ts.map +1 -0
- package/dist/storage/s3.js +67 -0
- package/dist/test-observability.d.ts +12 -0
- package/dist/test-observability.d.ts.map +1 -0
- package/dist/test-observability.js +153 -0
- package/dist/upload/errors.d.ts +16 -0
- package/dist/upload/errors.d.ts.map +1 -0
- package/dist/upload/errors.js +107 -0
- package/dist/upload/index.d.ts +6 -0
- package/dist/upload/index.d.ts.map +1 -0
- package/dist/upload/index.js +6 -0
- package/dist/upload/layers.d.ts +32 -0
- package/dist/upload/layers.d.ts.map +1 -0
- package/dist/upload/layers.js +63 -0
- package/dist/upload/metrics.d.ts +46 -0
- package/dist/upload/metrics.d.ts.map +1 -0
- package/dist/upload/metrics.js +80 -0
- package/dist/upload/testing.d.ts +32 -0
- package/dist/upload/testing.d.ts.map +1 -0
- package/dist/upload/testing.js +52 -0
- package/dist/upload/tracing.d.ts +25 -0
- package/dist/upload/tracing.d.ts.map +1 -0
- package/dist/upload/tracing.js +35 -0
- package/package.json +37 -0
- package/src/core/errors.ts +187 -0
- package/src/core/index.ts +9 -0
- package/src/core/layers.ts +205 -0
- package/src/core/logging.ts +81 -0
- package/src/core/metrics.ts +108 -0
- package/src/core/testing.ts +142 -0
- package/src/core/tracing.ts +67 -0
- package/src/core/utilities.ts +133 -0
- package/src/flow/errors.ts +95 -0
- package/src/flow/index.ts +17 -0
- package/src/flow/layers.ts +131 -0
- package/src/flow/metrics.ts +130 -0
- package/src/flow/testing.ts +33 -0
- package/src/flow/tracing.ts +72 -0
- package/src/index.ts +31 -0
- package/src/service/metrics.ts +31 -0
- package/src/storage/azure.ts +163 -0
- package/src/storage/filesystem.ts +153 -0
- package/src/storage/gcs.ts +161 -0
- package/src/storage/index.ts +5 -0
- package/src/storage/s3.ts +136 -0
- package/src/test-observability.ts +234 -0
- package/src/upload/errors.ts +166 -0
- package/src/upload/index.ts +12 -0
- package/src/upload/layers.ts +88 -0
- package/src/upload/metrics.ts +118 -0
- package/src/upload/testing.ts +60 -0
- package/src/upload/tracing.ts +58 -0
- package/tsconfig.json +10 -0
- 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,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
|
+
});
|