@elto/telemetry 0.1.0
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/LICENSE +176 -0
- package/README.md +284 -0
- package/dist/index.cjs +19 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +91 -0
- package/dist/index.d.ts +91 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -0
- package/dist/node/index.cjs +204 -0
- package/dist/node/index.cjs.map +1 -0
- package/dist/node/index.d.cts +91 -0
- package/dist/node/index.d.ts +91 -0
- package/dist/node/index.js +175 -0
- package/dist/node/index.js.map +1 -0
- package/dist/web/index.cjs +278 -0
- package/dist/web/index.cjs.map +1 -0
- package/dist/web/index.d.cts +230 -0
- package/dist/web/index.d.ts +230 -0
- package/dist/web/index.js +255 -0
- package/dist/web/index.js.map +1 -0
- package/package.json +91 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import * as _effect_opentelemetry_Resource from '@effect/opentelemetry/Resource';
|
|
2
|
+
import { Layer } from 'effect';
|
|
3
|
+
|
|
4
|
+
interface PIIConfig {
|
|
5
|
+
/**
|
|
6
|
+
* Set of span attribute keys whose values will be redacted.
|
|
7
|
+
*/
|
|
8
|
+
piiAttributeKeys?: Set<string>;
|
|
9
|
+
/**
|
|
10
|
+
* Redacted value placeholder.
|
|
11
|
+
*
|
|
12
|
+
* @default "[redacted]"
|
|
13
|
+
*/
|
|
14
|
+
redactedValue?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Resource configuration for OpenTelemetry
|
|
18
|
+
*/
|
|
19
|
+
interface ResourceConfig {
|
|
20
|
+
serviceName: string;
|
|
21
|
+
serviceVersion: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Endpoint configuration for OpenTelemetry exporters
|
|
25
|
+
*/
|
|
26
|
+
interface EndpointConfig {
|
|
27
|
+
traces: string;
|
|
28
|
+
logs: string;
|
|
29
|
+
metrics?: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Configuration for Web OpenTelemetry SDK
|
|
33
|
+
*/
|
|
34
|
+
interface WebSdkConfig {
|
|
35
|
+
resource: ResourceConfig;
|
|
36
|
+
endpoints: EndpointConfig;
|
|
37
|
+
getEndpoints?: () => EndpointConfig;
|
|
38
|
+
piiConfig?: PIIConfig;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Proxy mode configuration for browser telemetry
|
|
42
|
+
*/
|
|
43
|
+
interface ProxyConfig {
|
|
44
|
+
serverUrl: string;
|
|
45
|
+
tracesPath?: string;
|
|
46
|
+
logsPath?: string;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* OTel mode for browser telemetry,
|
|
50
|
+
*
|
|
51
|
+
* - "direct": Send telemetry directly to the OpenTelemetry collector
|
|
52
|
+
* - "proxy": Send telemetry through your server
|
|
53
|
+
*/
|
|
54
|
+
type OtelMode = "direct" | "proxy";
|
|
55
|
+
/**
|
|
56
|
+
* Configuration for browser logger
|
|
57
|
+
*/
|
|
58
|
+
interface BrowserLoggerConfig {
|
|
59
|
+
resource: ResourceConfig;
|
|
60
|
+
logsEndpoint: string | (() => string);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Log level for browser logger
|
|
64
|
+
*/
|
|
65
|
+
type LogLevel = "debug" | "info" | "warning" | "error";
|
|
66
|
+
/**
|
|
67
|
+
* Options for logging
|
|
68
|
+
*/
|
|
69
|
+
interface LogOptions {
|
|
70
|
+
level: LogLevel;
|
|
71
|
+
message: string;
|
|
72
|
+
annotations?: Record<string, string | number | boolean>;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Browser logger interface
|
|
76
|
+
*/
|
|
77
|
+
interface BrowserLogger {
|
|
78
|
+
debug: (message: string, annotations?: Record<string, string | number | boolean>) => void;
|
|
79
|
+
info: (message: string, annotations?: Record<string, string | number | boolean>) => void;
|
|
80
|
+
warning: (message: string, annotations?: Record<string, string | number | boolean>) => void;
|
|
81
|
+
error: (message: string, annotations?: Record<string, string | number | boolean>) => void;
|
|
82
|
+
flush: () => Promise<void>;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Get the current OpenTelemetry mode from localStorage
|
|
87
|
+
* @returns The current mode ("direct" or "proxy")
|
|
88
|
+
*/
|
|
89
|
+
declare function getOtelMode(): OtelMode;
|
|
90
|
+
/**
|
|
91
|
+
* Set the OpenTelemetry mode in localStorage
|
|
92
|
+
* @param mode - The mode to set
|
|
93
|
+
*
|
|
94
|
+
* @see {@link OtelMode}
|
|
95
|
+
*/
|
|
96
|
+
declare function setOtelMode(mode: OtelMode): void;
|
|
97
|
+
/**
|
|
98
|
+
* Create endpoint URLs for direct connection to OpenTelemetry collector
|
|
99
|
+
*
|
|
100
|
+
* @param baseUrl - Base URL of the OpenTelemetry collector (e.g., "http://localhost:4318")
|
|
101
|
+
* @returns Endpoint configuration object
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```typescript
|
|
105
|
+
* const endpoints = createDirectEndpoints("http://localhost:4318");
|
|
106
|
+
* // Returns: {
|
|
107
|
+
* // traces: "http://localhost:4318/v1/traces",
|
|
108
|
+
* // logs: "http://localhost:4318/v1/logs"
|
|
109
|
+
* // }
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
declare function createDirectEndpoints(baseUrl: string): EndpointConfig;
|
|
113
|
+
/**
|
|
114
|
+
* Create endpoint URLs for proxy mode (telemetry sent through server)
|
|
115
|
+
*
|
|
116
|
+
* @param config - Proxy configuration
|
|
117
|
+
* @returns Endpoint configuration object
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* ```typescript
|
|
121
|
+
* const endpoints = createProxyEndpoints({
|
|
122
|
+
* serverUrl: "https://api.example.com",
|
|
123
|
+
* tracesPath: "/api/events", // optional, defaults to "/api/events"
|
|
124
|
+
* logsPath: "/api/signals", // optional, defaults to "/api/signals"
|
|
125
|
+
* });
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
128
|
+
declare function createProxyEndpoints(config: ProxyConfig): EndpointConfig;
|
|
129
|
+
/**
|
|
130
|
+
* Create a function that returns endpoints based on the current mode
|
|
131
|
+
*
|
|
132
|
+
* @param directUrl - Base URL for direct mode
|
|
133
|
+
* @param proxyConfig - Configuration for proxy mode
|
|
134
|
+
* @returns A function that returns the appropriate endpoints based on the current mode
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* ```typescript
|
|
138
|
+
* const getEndpoints = createModeBasedEndpoints(
|
|
139
|
+
* "http://localhost:4318",
|
|
140
|
+
* { serverUrl: "https://api.example.com" }
|
|
141
|
+
* );
|
|
142
|
+
* ```
|
|
143
|
+
*/
|
|
144
|
+
declare function createModeBasedEndpoints(directUrl: string, proxyConfig: ProxyConfig): () => EndpointConfig;
|
|
145
|
+
/**
|
|
146
|
+
* Create an OpenTelemetry Layer for browser applications
|
|
147
|
+
*
|
|
148
|
+
* This layer configures:
|
|
149
|
+
* - OTLP HTTP exporter for traces (to Jaeger)
|
|
150
|
+
* - OTLP HTTP exporter for logs (to Loki)
|
|
151
|
+
* - Custom span processor for redaction/filtering before export
|
|
152
|
+
* - Batch processors for efficient batching
|
|
153
|
+
* - Zone context manager for async context propagation in the browser
|
|
154
|
+
*
|
|
155
|
+
* Effect RPC automatically propagates trace context (traceId, spanId, sampled)
|
|
156
|
+
* in the RPC message payload, enabling distributed tracing without manual header injection.
|
|
157
|
+
*
|
|
158
|
+
* @param config - Configuration object
|
|
159
|
+
* @returns Effect Layer for OpenTelemetry
|
|
160
|
+
*
|
|
161
|
+
* @example
|
|
162
|
+
* ```typescript
|
|
163
|
+
* import { createWebOtelLayer, createModeBasedEndpoints } from "@elto/telemetry/web";
|
|
164
|
+
*
|
|
165
|
+
* const OtelLive = createWebOtelLayer({
|
|
166
|
+
* resource: {
|
|
167
|
+
* serviceName: "web-client",
|
|
168
|
+
* serviceVersion: "1.0.0",
|
|
169
|
+
* },
|
|
170
|
+
* endpoints: { traces: "", logs: "" }, // fallback
|
|
171
|
+
* getEndpoints: createModeBasedEndpoints(
|
|
172
|
+
* "http://localhost:4318",
|
|
173
|
+
* { serverUrl: "https://api.example.com" }
|
|
174
|
+
* ),
|
|
175
|
+
* piiConfig: {
|
|
176
|
+
* piiAttributeKeys: new Set(["user.email", "user.phone"]),
|
|
177
|
+
* },
|
|
178
|
+
* });
|
|
179
|
+
* ```
|
|
180
|
+
*/
|
|
181
|
+
declare function createWebOtelLayer(config: WebSdkConfig): Layer.Layer<_effect_opentelemetry_Resource.Resource, never, never>;
|
|
182
|
+
/**
|
|
183
|
+
* Combined layer for RPC client with OpenTelemetry enabled.
|
|
184
|
+
* Use this layer when creating the RPC client to enable distributed tracing.
|
|
185
|
+
*
|
|
186
|
+
* @param otelLayer - The OpenTelemetry layer
|
|
187
|
+
* @param layer - The layer to merge with OpenTelemetry
|
|
188
|
+
* @returns Combined layer
|
|
189
|
+
*
|
|
190
|
+
* @example
|
|
191
|
+
* ```typescript
|
|
192
|
+
* import { withOtel } from "@elto/telemetry/web";
|
|
193
|
+
*
|
|
194
|
+
* const ClientLive = withOtel(OtelLive, RpcClientLayer);
|
|
195
|
+
* ```
|
|
196
|
+
*/
|
|
197
|
+
declare function withOtel<R, E, A>(otelLayer: Layer.Layer<R, E, A>, layer: Layer.Layer<any, any, any>): Layer.Layer<any, any, A | Exclude<any, R>>;
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Create a browser logger that sends logs to OpenTelemetry collector
|
|
201
|
+
*
|
|
202
|
+
* This logger can be used in browser contexts where Effect is not available.
|
|
203
|
+
* It uses the OpenTelemetry Logs API directly for browser-to-collector logging.
|
|
204
|
+
*
|
|
205
|
+
* @param config - Logger configuration
|
|
206
|
+
* @returns Browser logger instance
|
|
207
|
+
*
|
|
208
|
+
* @example
|
|
209
|
+
* ```typescript
|
|
210
|
+
* import { createBrowserLogger, getOtelMode } from "@elto/telemetry/web";
|
|
211
|
+
*
|
|
212
|
+
* const getEndpoint = () =>
|
|
213
|
+
* getOtelMode() === "proxy"
|
|
214
|
+
* ? "https://api.example.com/api/signals"
|
|
215
|
+
* : "http://localhost:4318/v1/logs";
|
|
216
|
+
*
|
|
217
|
+
* const logger = createBrowserLogger({
|
|
218
|
+
* resource: {
|
|
219
|
+
* serviceName: "web-client",
|
|
220
|
+
* serviceVersion: "1.0.0",
|
|
221
|
+
* },
|
|
222
|
+
* logsEndpoint: getEndpoint,
|
|
223
|
+
* });
|
|
224
|
+
*
|
|
225
|
+
* logger.info("User logged in", { userId: "123" });
|
|
226
|
+
* ```
|
|
227
|
+
*/
|
|
228
|
+
declare function createBrowserLogger(config: BrowserLoggerConfig): BrowserLogger;
|
|
229
|
+
|
|
230
|
+
export { type BrowserLogger, type BrowserLoggerConfig, type EndpointConfig, type LogLevel, type LogOptions, type OtelMode, type ProxyConfig, type ResourceConfig, type WebSdkConfig, createBrowserLogger, createDirectEndpoints, createModeBasedEndpoints, createProxyEndpoints, createWebOtelLayer, getOtelMode, setOtelMode, withOtel };
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
// src/web/sdk.ts
|
|
2
|
+
import { WebSdk } from "@effect/opentelemetry";
|
|
3
|
+
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
|
|
4
|
+
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
|
|
5
|
+
import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-web";
|
|
6
|
+
import { BatchLogRecordProcessor } from "@opentelemetry/sdk-logs";
|
|
7
|
+
import { ZoneContextManager } from "@opentelemetry/context-zone";
|
|
8
|
+
import { Layer } from "effect";
|
|
9
|
+
|
|
10
|
+
// src/common/span-processors.ts
|
|
11
|
+
var DROP_ATTRIBUTE_KEY = "telemetry.drop";
|
|
12
|
+
var REDACTED_VALUE = "[redacted]";
|
|
13
|
+
var STATEMENT_MAX_LENGTH = 12;
|
|
14
|
+
var STATEMENT_HEAD_LENGTH = 3;
|
|
15
|
+
var STATEMENT_TAIL_LENGTH = 3;
|
|
16
|
+
var PII_ATTRIBUTE_KEYS = /* @__PURE__ */ new Set(["user.email", "note.id", "profile.id"]);
|
|
17
|
+
var getAttributes = (span) => span.attributes ?? {};
|
|
18
|
+
var setAttribute = (span, key, value) => {
|
|
19
|
+
const target = span;
|
|
20
|
+
if (typeof target.setAttribute === "function") {
|
|
21
|
+
target.setAttribute(key, value);
|
|
22
|
+
}
|
|
23
|
+
if (target.attributes) {
|
|
24
|
+
target.attributes[key] = value;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
var hashString = (value) => {
|
|
28
|
+
let hash = 2166136261;
|
|
29
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
30
|
+
hash ^= value.charCodeAt(index);
|
|
31
|
+
hash = Math.imul(hash, 16777619);
|
|
32
|
+
}
|
|
33
|
+
return (hash >>> 0).toString(16).padStart(8, "0");
|
|
34
|
+
};
|
|
35
|
+
var hashAttributeValue = (value) => {
|
|
36
|
+
if (Array.isArray(value)) {
|
|
37
|
+
return hashString(value.map((item) => String(item)).join("|"));
|
|
38
|
+
}
|
|
39
|
+
return hashString(String(value));
|
|
40
|
+
};
|
|
41
|
+
var redactConnectionString = (value) => value.replace(/\/\/([^@/]+)@/, "//***:***@");
|
|
42
|
+
var stripUrlQuery = (value) => value.split("?")[0] ?? value;
|
|
43
|
+
var shortenStatement = (statement) => {
|
|
44
|
+
if (statement.length <= STATEMENT_MAX_LENGTH) {
|
|
45
|
+
return statement;
|
|
46
|
+
}
|
|
47
|
+
const head = statement.slice(0, STATEMENT_HEAD_LENGTH);
|
|
48
|
+
const tail = statement.slice(-STATEMENT_TAIL_LENGTH);
|
|
49
|
+
const removed = statement.length - (STATEMENT_HEAD_LENGTH + STATEMENT_TAIL_LENGTH);
|
|
50
|
+
return `${head}...${tail} (+ ${removed} characters)`;
|
|
51
|
+
};
|
|
52
|
+
var shouldDropSpan = (span) => {
|
|
53
|
+
const attributes = getAttributes(span);
|
|
54
|
+
const drop = attributes[DROP_ATTRIBUTE_KEY];
|
|
55
|
+
return drop === true || drop === "true";
|
|
56
|
+
};
|
|
57
|
+
var sanitizeAttributes = (span, piiAttributes) => {
|
|
58
|
+
const attributes = getAttributes(span);
|
|
59
|
+
let redactions = 0;
|
|
60
|
+
let transformed = 0;
|
|
61
|
+
const piiKeys = piiAttributes ?? PII_ATTRIBUTE_KEYS;
|
|
62
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
63
|
+
if (value === void 0) {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (piiKeys.has(key)) {
|
|
67
|
+
setAttribute(span, `${key}_hash`, hashAttributeValue(value));
|
|
68
|
+
setAttribute(span, key, REDACTED_VALUE);
|
|
69
|
+
redactions += 1;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (key === "db.connection_string" && typeof value === "string") {
|
|
73
|
+
setAttribute(span, "db.connection_string_redacted", redactConnectionString(value));
|
|
74
|
+
setAttribute(span, key, REDACTED_VALUE);
|
|
75
|
+
redactions += 1;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (key === "db.statement" && typeof value === "string") {
|
|
79
|
+
const shortened = shortenStatement(value);
|
|
80
|
+
if (shortened !== value) {
|
|
81
|
+
setAttribute(span, key, shortened);
|
|
82
|
+
transformed += 1;
|
|
83
|
+
}
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (key === "http.url" && typeof value === "string") {
|
|
87
|
+
const sanitized = stripUrlQuery(value);
|
|
88
|
+
if (sanitized !== value) {
|
|
89
|
+
setAttribute(span, key, sanitized);
|
|
90
|
+
transformed += 1;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const statusCode = attributes["http.status_code"];
|
|
95
|
+
if (typeof statusCode === "number") {
|
|
96
|
+
setAttribute(span, "http.status_category", `${Math.floor(statusCode / 100)}xx`);
|
|
97
|
+
transformed += 1;
|
|
98
|
+
}
|
|
99
|
+
if (redactions > 0 || transformed > 0) {
|
|
100
|
+
setAttribute(span, "telemetry.redactions", redactions);
|
|
101
|
+
setAttribute(span, "telemetry.transformations", transformed);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
var CustomSpanProcessor = class {
|
|
105
|
+
constructor(delegate, piiAttributes) {
|
|
106
|
+
this.delegate = delegate;
|
|
107
|
+
this.piiAttributes = piiAttributes;
|
|
108
|
+
}
|
|
109
|
+
onStart(span, parentContext) {
|
|
110
|
+
sanitizeAttributes(span, this.piiAttributes);
|
|
111
|
+
this.delegate.onStart(span, parentContext);
|
|
112
|
+
}
|
|
113
|
+
onEnd(span) {
|
|
114
|
+
sanitizeAttributes(span, this.piiAttributes);
|
|
115
|
+
if (shouldDropSpan(span)) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
this.delegate.onEnd(span);
|
|
119
|
+
}
|
|
120
|
+
shutdown() {
|
|
121
|
+
return this.delegate.shutdown();
|
|
122
|
+
}
|
|
123
|
+
forceFlush() {
|
|
124
|
+
return this.delegate.forceFlush();
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
var createCustomSpanProcessor = (delegate, piiConfig) => new CustomSpanProcessor(delegate, piiConfig?.piiAttributeKeys);
|
|
128
|
+
|
|
129
|
+
// src/web/sdk.ts
|
|
130
|
+
var OTEL_MODE_KEY = "otel-mode";
|
|
131
|
+
function getOtelMode() {
|
|
132
|
+
if (typeof localStorage === "undefined") {
|
|
133
|
+
return "direct";
|
|
134
|
+
}
|
|
135
|
+
return localStorage.getItem(OTEL_MODE_KEY) || "direct";
|
|
136
|
+
}
|
|
137
|
+
function setOtelMode(mode) {
|
|
138
|
+
if (typeof localStorage !== "undefined") {
|
|
139
|
+
localStorage.setItem(OTEL_MODE_KEY, mode);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function createDirectEndpoints(baseUrl) {
|
|
143
|
+
return {
|
|
144
|
+
traces: `${baseUrl}/v1/traces`,
|
|
145
|
+
logs: `${baseUrl}/v1/logs`
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
function createProxyEndpoints(config) {
|
|
149
|
+
const { serverUrl, tracesPath = "/api/events", logsPath = "/api/signals" } = config;
|
|
150
|
+
return {
|
|
151
|
+
traces: `${serverUrl}${tracesPath}`,
|
|
152
|
+
logs: `${serverUrl}${logsPath}`
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function createModeBasedEndpoints(directUrl, proxyConfig) {
|
|
156
|
+
return () => {
|
|
157
|
+
const mode = getOtelMode();
|
|
158
|
+
return mode === "proxy" ? createProxyEndpoints(proxyConfig) : createDirectEndpoints(directUrl);
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function createWebOtelLayer(config) {
|
|
162
|
+
const { resource, endpoints, getEndpoints } = config;
|
|
163
|
+
return WebSdk.layer(() => {
|
|
164
|
+
const activeEndpoints = getEndpoints ? getEndpoints() : endpoints;
|
|
165
|
+
return {
|
|
166
|
+
resource: {
|
|
167
|
+
serviceName: resource.serviceName,
|
|
168
|
+
serviceVersion: resource.serviceVersion
|
|
169
|
+
},
|
|
170
|
+
spanProcessor: createCustomSpanProcessor(
|
|
171
|
+
new BatchSpanProcessor(
|
|
172
|
+
new OTLPTraceExporter({
|
|
173
|
+
url: activeEndpoints.traces
|
|
174
|
+
})
|
|
175
|
+
),
|
|
176
|
+
config.piiConfig
|
|
177
|
+
),
|
|
178
|
+
logRecordProcessor: new BatchLogRecordProcessor(
|
|
179
|
+
new OTLPLogExporter({
|
|
180
|
+
url: activeEndpoints.logs
|
|
181
|
+
})
|
|
182
|
+
),
|
|
183
|
+
contextManager: new ZoneContextManager()
|
|
184
|
+
};
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
function withOtel(otelLayer, layer) {
|
|
188
|
+
return Layer.provideMerge(layer, otelLayer);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// src/web/logger.ts
|
|
192
|
+
import { logs, SeverityNumber } from "@opentelemetry/api-logs";
|
|
193
|
+
import { LoggerProvider, BatchLogRecordProcessor as BatchLogRecordProcessor2 } from "@opentelemetry/sdk-logs";
|
|
194
|
+
import { OTLPLogExporter as OTLPLogExporter2 } from "@opentelemetry/exporter-logs-otlp-http";
|
|
195
|
+
import { resourceFromAttributes } from "@opentelemetry/resources";
|
|
196
|
+
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from "@opentelemetry/semantic-conventions";
|
|
197
|
+
var severityMap = {
|
|
198
|
+
debug: SeverityNumber.DEBUG,
|
|
199
|
+
info: SeverityNumber.INFO,
|
|
200
|
+
warning: SeverityNumber.WARN,
|
|
201
|
+
error: SeverityNumber.ERROR
|
|
202
|
+
};
|
|
203
|
+
var severityTextMap = {
|
|
204
|
+
debug: "DEBUG",
|
|
205
|
+
info: "INFO",
|
|
206
|
+
warning: "WARN",
|
|
207
|
+
error: "ERROR"
|
|
208
|
+
};
|
|
209
|
+
function createBrowserLogger(config) {
|
|
210
|
+
const { resource, logsEndpoint } = config;
|
|
211
|
+
const endpoint = typeof logsEndpoint === "function" ? logsEndpoint() : logsEndpoint;
|
|
212
|
+
const logExporter = new OTLPLogExporter2({
|
|
213
|
+
url: endpoint
|
|
214
|
+
});
|
|
215
|
+
const logRecordProcessor = new BatchLogRecordProcessor2(logExporter);
|
|
216
|
+
const loggerProvider = new LoggerProvider({
|
|
217
|
+
resource: resourceFromAttributes({
|
|
218
|
+
[ATTR_SERVICE_NAME]: resource.serviceName,
|
|
219
|
+
[ATTR_SERVICE_VERSION]: resource.serviceVersion
|
|
220
|
+
}),
|
|
221
|
+
processors: [logRecordProcessor]
|
|
222
|
+
});
|
|
223
|
+
logs.setGlobalLoggerProvider(loggerProvider);
|
|
224
|
+
const otelLogger = loggerProvider.getLogger(resource.serviceName, resource.serviceVersion);
|
|
225
|
+
function sendLog(options) {
|
|
226
|
+
const { level, message, annotations = {} } = options;
|
|
227
|
+
otelLogger.emit({
|
|
228
|
+
severityNumber: severityMap[level],
|
|
229
|
+
severityText: severityTextMap[level],
|
|
230
|
+
body: message,
|
|
231
|
+
attributes: annotations
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
async function flushLogs() {
|
|
235
|
+
await loggerProvider.forceFlush();
|
|
236
|
+
}
|
|
237
|
+
return {
|
|
238
|
+
debug: (message, annotations) => sendLog({ level: "debug", message, ...annotations && { annotations } }),
|
|
239
|
+
info: (message, annotations) => sendLog({ level: "info", message, ...annotations && { annotations } }),
|
|
240
|
+
warning: (message, annotations) => sendLog({ level: "warning", message, ...annotations && { annotations } }),
|
|
241
|
+
error: (message, annotations) => sendLog({ level: "error", message, ...annotations && { annotations } }),
|
|
242
|
+
flush: flushLogs
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
export {
|
|
246
|
+
createBrowserLogger,
|
|
247
|
+
createDirectEndpoints,
|
|
248
|
+
createModeBasedEndpoints,
|
|
249
|
+
createProxyEndpoints,
|
|
250
|
+
createWebOtelLayer,
|
|
251
|
+
getOtelMode,
|
|
252
|
+
setOtelMode,
|
|
253
|
+
withOtel
|
|
254
|
+
};
|
|
255
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/web/sdk.ts","../../src/common/span-processors.ts","../../src/web/logger.ts"],"sourcesContent":["import { WebSdk } from \"@effect/opentelemetry\";\nimport { OTLPTraceExporter } from \"@opentelemetry/exporter-trace-otlp-http\";\nimport { OTLPLogExporter } from \"@opentelemetry/exporter-logs-otlp-http\";\nimport { BatchSpanProcessor } from \"@opentelemetry/sdk-trace-web\";\nimport { BatchLogRecordProcessor } from \"@opentelemetry/sdk-logs\";\nimport { ZoneContextManager } from \"@opentelemetry/context-zone\";\nimport { Layer } from \"effect\";\nimport { createCustomSpanProcessor } from \"../common/span-processors.js\";\nimport type { WebSdkConfig, EndpointConfig, ProxyConfig, OtelMode } from \"../common/types.js\";\n\nconst OTEL_MODE_KEY = \"otel-mode\";\n\n/**\n * Get the current OpenTelemetry mode from localStorage\n * @returns The current mode (\"direct\" or \"proxy\")\n */\nexport function getOtelMode(): OtelMode {\n if (typeof localStorage === \"undefined\") {\n return \"direct\";\n }\n return (localStorage.getItem(OTEL_MODE_KEY) || \"direct\") as OtelMode;\n}\n\n/**\n * Set the OpenTelemetry mode in localStorage\n * @param mode - The mode to set\n *\n * @see {@link OtelMode}\n */\nexport function setOtelMode(mode: OtelMode): void {\n if (typeof localStorage !== \"undefined\") {\n localStorage.setItem(OTEL_MODE_KEY, mode);\n }\n}\n\n/**\n * Create endpoint URLs for direct connection to OpenTelemetry collector\n *\n * @param baseUrl - Base URL of the OpenTelemetry collector (e.g., \"http://localhost:4318\")\n * @returns Endpoint configuration object\n *\n * @example\n * ```typescript\n * const endpoints = createDirectEndpoints(\"http://localhost:4318\");\n * // Returns: {\n * // traces: \"http://localhost:4318/v1/traces\",\n * // logs: \"http://localhost:4318/v1/logs\"\n * // }\n * ```\n */\nexport function createDirectEndpoints(baseUrl: string): EndpointConfig {\n return {\n traces: `${baseUrl}/v1/traces`,\n logs: `${baseUrl}/v1/logs`,\n };\n}\n\n/**\n * Create endpoint URLs for proxy mode (telemetry sent through server)\n *\n * @param config - Proxy configuration\n * @returns Endpoint configuration object\n *\n * @example\n * ```typescript\n * const endpoints = createProxyEndpoints({\n * serverUrl: \"https://api.example.com\",\n * tracesPath: \"/api/events\", // optional, defaults to \"/api/events\"\n * logsPath: \"/api/signals\", // optional, defaults to \"/api/signals\"\n * });\n * ```\n */\nexport function createProxyEndpoints(config: ProxyConfig): EndpointConfig {\n const { serverUrl, tracesPath = \"/api/events\", logsPath = \"/api/signals\" } = config;\n return {\n traces: `${serverUrl}${tracesPath}`,\n logs: `${serverUrl}${logsPath}`,\n };\n}\n\n/**\n * Create a function that returns endpoints based on the current mode\n *\n * @param directUrl - Base URL for direct mode\n * @param proxyConfig - Configuration for proxy mode\n * @returns A function that returns the appropriate endpoints based on the current mode\n *\n * @example\n * ```typescript\n * const getEndpoints = createModeBasedEndpoints(\n * \"http://localhost:4318\",\n * { serverUrl: \"https://api.example.com\" }\n * );\n * ```\n */\nexport function createModeBasedEndpoints(\n directUrl: string,\n proxyConfig: ProxyConfig,\n): () => EndpointConfig {\n return () => {\n const mode = getOtelMode();\n return mode === \"proxy\" ? createProxyEndpoints(proxyConfig) : createDirectEndpoints(directUrl);\n };\n}\n\n/**\n * Create an OpenTelemetry Layer for browser applications\n *\n * This layer configures:\n * - OTLP HTTP exporter for traces (to Jaeger)\n * - OTLP HTTP exporter for logs (to Loki)\n * - Custom span processor for redaction/filtering before export\n * - Batch processors for efficient batching\n * - Zone context manager for async context propagation in the browser\n *\n * Effect RPC automatically propagates trace context (traceId, spanId, sampled)\n * in the RPC message payload, enabling distributed tracing without manual header injection.\n *\n * @param config - Configuration object\n * @returns Effect Layer for OpenTelemetry\n *\n * @example\n * ```typescript\n * import { createWebOtelLayer, createModeBasedEndpoints } from \"@elto/telemetry/web\";\n *\n * const OtelLive = createWebOtelLayer({\n * resource: {\n * serviceName: \"web-client\",\n * serviceVersion: \"1.0.0\",\n * },\n * endpoints: { traces: \"\", logs: \"\" }, // fallback\n * getEndpoints: createModeBasedEndpoints(\n * \"http://localhost:4318\",\n * { serverUrl: \"https://api.example.com\" }\n * ),\n * piiConfig: {\n * piiAttributeKeys: new Set([\"user.email\", \"user.phone\"]),\n * },\n * });\n * ```\n */\nexport function createWebOtelLayer(config: WebSdkConfig) {\n const { resource, endpoints, getEndpoints } = config;\n\n return WebSdk.layer(() => {\n const activeEndpoints = getEndpoints ? getEndpoints() : endpoints;\n\n return {\n resource: {\n serviceName: resource.serviceName,\n serviceVersion: resource.serviceVersion,\n },\n spanProcessor: createCustomSpanProcessor(\n new BatchSpanProcessor(\n new OTLPTraceExporter({\n url: activeEndpoints.traces,\n }),\n ),\n config.piiConfig,\n ),\n logRecordProcessor: new BatchLogRecordProcessor(\n new OTLPLogExporter({\n url: activeEndpoints.logs,\n }),\n ),\n contextManager: new ZoneContextManager(),\n };\n });\n}\n\n/**\n * Combined layer for RPC client with OpenTelemetry enabled.\n * Use this layer when creating the RPC client to enable distributed tracing.\n *\n * @param otelLayer - The OpenTelemetry layer\n * @param layer - The layer to merge with OpenTelemetry\n * @returns Combined layer\n *\n * @example\n * ```typescript\n * import { withOtel } from \"@elto/telemetry/web\";\n *\n * const ClientLive = withOtel(OtelLive, RpcClientLayer);\n * ```\n */\nexport function withOtel<R, E, A>(\n otelLayer: Layer.Layer<R, E, A>,\n layer: Layer.Layer<any, any, any>,\n) {\n return Layer.provideMerge(layer, otelLayer);\n}\n","import type { AttributeValue, Context } from \"@opentelemetry/api\";\nimport type { ReadableSpan, Span, SpanProcessor } from \"@opentelemetry/sdk-trace-base\";\nimport type { PIIConfig } from \"./types\";\n\n/**\n * Default span processor used by @elto/telemetry to normalize and sanitize\n * attributes before export. It is intentionally small and opinionated so\n * demo traces stay safe and readable.\n */\ntype SpanAttributes = Record<string, AttributeValue | undefined>;\n\nconst DROP_ATTRIBUTE_KEY = \"telemetry.drop\";\nconst REDACTED_VALUE = \"[redacted]\";\nconst STATEMENT_MAX_LENGTH = 12;\nconst STATEMENT_HEAD_LENGTH = 3;\nconst STATEMENT_TAIL_LENGTH = 3;\n\nconst PII_ATTRIBUTE_KEYS = new Set([\"user.email\", \"note.id\", \"profile.id\"]);\n\nconst getAttributes = (span: Span | ReadableSpan): SpanAttributes =>\n (span as { attributes?: SpanAttributes }).attributes ?? {};\n\nconst setAttribute = (span: Span | ReadableSpan, key: string, value: AttributeValue) => {\n const target = span as {\n setAttribute?: (attributeKey: string, attributeValue: AttributeValue) => void;\n attributes?: SpanAttributes;\n };\n\n if (typeof target.setAttribute === \"function\") {\n target.setAttribute(key, value);\n }\n\n if (target.attributes) {\n target.attributes[key] = value;\n }\n};\n\nconst hashString = (value: string) => {\n let hash = 2166136261;\n for (let index = 0; index < value.length; index += 1) {\n hash ^= value.charCodeAt(index);\n hash = Math.imul(hash, 16777619);\n }\n return (hash >>> 0).toString(16).padStart(8, \"0\");\n};\n\nconst hashAttributeValue = (value: AttributeValue) => {\n if (Array.isArray(value)) {\n return hashString(value.map((item) => String(item)).join(\"|\"));\n }\n return hashString(String(value));\n};\n\nconst redactConnectionString = (value: string) => value.replace(/\\/\\/([^@/]+)@/, \"//***:***@\");\n\nconst stripUrlQuery = (value: string) => value.split(\"?\")[0] ?? value;\n\n/**\n * Shorten a long SQL statement to a leading head + tail with a marker that\n * explains how many characters were removed.\n */\nconst shortenStatement = (statement: string) => {\n if (statement.length <= STATEMENT_MAX_LENGTH) {\n return statement;\n }\n\n const head = statement.slice(0, STATEMENT_HEAD_LENGTH);\n const tail = statement.slice(-STATEMENT_TAIL_LENGTH);\n const removed = statement.length - (STATEMENT_HEAD_LENGTH + STATEMENT_TAIL_LENGTH);\n return `${head}...${tail} (+ ${removed} characters)`;\n};\n\nconst shouldDropSpan = (span: ReadableSpan) => {\n const attributes = getAttributes(span);\n const drop = attributes[DROP_ATTRIBUTE_KEY];\n return drop === true || drop === \"true\";\n};\n\nconst sanitizeAttributes = (span: Span | ReadableSpan, piiAttributes?: Set<string>) => {\n const attributes = getAttributes(span);\n let redactions = 0;\n let transformed = 0;\n\n const piiKeys = piiAttributes ?? PII_ATTRIBUTE_KEYS;\n for (const [key, value] of Object.entries(attributes)) {\n if (value === undefined) {\n continue;\n }\n\n if (piiKeys.has(key)) {\n setAttribute(span, `${key}_hash`, hashAttributeValue(value));\n setAttribute(span, key, REDACTED_VALUE);\n redactions += 1;\n continue;\n }\n\n if (key === \"db.connection_string\" && typeof value === \"string\") {\n setAttribute(span, \"db.connection_string_redacted\", redactConnectionString(value));\n setAttribute(span, key, REDACTED_VALUE);\n redactions += 1;\n continue;\n }\n\n if (key === \"db.statement\" && typeof value === \"string\") {\n const shortened = shortenStatement(value);\n if (shortened !== value) {\n setAttribute(span, key, shortened);\n transformed += 1;\n }\n continue;\n }\n\n if (key === \"http.url\" && typeof value === \"string\") {\n const sanitized = stripUrlQuery(value);\n if (sanitized !== value) {\n setAttribute(span, key, sanitized);\n transformed += 1;\n }\n }\n }\n\n const statusCode = attributes[\"http.status_code\"];\n if (typeof statusCode === \"number\") {\n setAttribute(span, \"http.status_category\", `${Math.floor(statusCode / 100)}xx`);\n transformed += 1;\n }\n\n if (redactions > 0 || transformed > 0) {\n setAttribute(span, \"telemetry.redactions\", redactions);\n setAttribute(span, \"telemetry.transformations\", transformed);\n }\n};\n\n/**\n * A SpanProcessor that applies redaction, attribute normalization, and optional\n * dropping before delegating to another processor (typically a BatchSpanProcessor).\n *\n * Behavior:\n * - Redacts `user.email`, `note.id`, and `profile.id` while adding `<key>_hash`.\n * - Redacts `db.connection_string` and stores a sanitized copy in\n * `db.connection_string_redacted`.\n * - Shortens `db.statement` when it exceeds the configured threshold.\n * - Strips query strings from `http.url`.\n * - Adds `http.status_category` when `http.status_code` is present.\n * - Adds `telemetry.redactions` and `telemetry.transformations` counters.\n * - Drops spans when `telemetry.drop` is `true` or `\"true\"`.\n *\n * @param delegate - The SpanProcessor to delegate to after sanitization\n * @param piiAttributes - Optional set of span attribute keys whose values will be redacted.\n * If provided, this overrides the default {@link PII_ATTRIBUTE_KEYS} (`user.email`, `note.id`, `profile.id`).\n * For each matching key, the original value is replaced with `[redacted]` and a hash\n * is stored in a `<key>_hash` attribute.\n */\nexport class CustomSpanProcessor implements SpanProcessor {\n constructor(\n private readonly delegate: SpanProcessor,\n private readonly piiAttributes?: Set<string>,\n ) {}\n\n onStart(span: Span, parentContext: Context): void {\n sanitizeAttributes(span, this.piiAttributes);\n this.delegate.onStart(span, parentContext);\n }\n\n onEnd(span: ReadableSpan): void {\n sanitizeAttributes(span, this.piiAttributes);\n if (shouldDropSpan(span)) {\n return;\n }\n this.delegate.onEnd(span);\n }\n\n shutdown(): Promise<void> {\n return this.delegate.shutdown();\n }\n\n forceFlush(): Promise<void> {\n return this.delegate.forceFlush();\n }\n}\n\n/**\n * Convenience factory for wrapping a delegate SpanProcessor with the\n * @elto/telemetry custom sanitation behavior.\n */\nexport const createCustomSpanProcessor = (delegate: SpanProcessor, piiConfig?: PIIConfig) =>\n new CustomSpanProcessor(delegate, piiConfig?.piiAttributeKeys);\n","import { logs, SeverityNumber } from \"@opentelemetry/api-logs\";\nimport { LoggerProvider, BatchLogRecordProcessor } from \"@opentelemetry/sdk-logs\";\nimport { OTLPLogExporter } from \"@opentelemetry/exporter-logs-otlp-http\";\nimport { resourceFromAttributes } from \"@opentelemetry/resources\";\nimport { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from \"@opentelemetry/semantic-conventions\";\nimport type { BrowserLoggerConfig, LogLevel, LogOptions, BrowserLogger } from \"../common/types.js\";\n\nconst severityMap: Record<LogLevel, SeverityNumber> = {\n debug: SeverityNumber.DEBUG,\n info: SeverityNumber.INFO,\n warning: SeverityNumber.WARN,\n error: SeverityNumber.ERROR,\n};\n\nconst severityTextMap: Record<LogLevel, string> = {\n debug: \"DEBUG\",\n info: \"INFO\",\n warning: \"WARN\",\n error: \"ERROR\",\n};\n\n/**\n * Create a browser logger that sends logs to OpenTelemetry collector\n *\n * This logger can be used in browser contexts where Effect is not available.\n * It uses the OpenTelemetry Logs API directly for browser-to-collector logging.\n *\n * @param config - Logger configuration\n * @returns Browser logger instance\n *\n * @example\n * ```typescript\n * import { createBrowserLogger, getOtelMode } from \"@elto/telemetry/web\";\n *\n * const getEndpoint = () =>\n * getOtelMode() === \"proxy\"\n * ? \"https://api.example.com/api/signals\"\n * : \"http://localhost:4318/v1/logs\";\n *\n * const logger = createBrowserLogger({\n * resource: {\n * serviceName: \"web-client\",\n * serviceVersion: \"1.0.0\",\n * },\n * logsEndpoint: getEndpoint,\n * });\n *\n * logger.info(\"User logged in\", { userId: \"123\" });\n * ```\n */\nexport function createBrowserLogger(config: BrowserLoggerConfig): BrowserLogger {\n const { resource, logsEndpoint } = config;\n\n // Resolve endpoint if it's a function\n const endpoint = typeof logsEndpoint === \"function\" ? logsEndpoint() : logsEndpoint;\n\n const logExporter = new OTLPLogExporter({\n url: endpoint,\n });\n\n const logRecordProcessor = new BatchLogRecordProcessor(logExporter);\n\n // Create a dedicated logger provider for browser logs with proper resource identification\n const loggerProvider = new LoggerProvider({\n resource: resourceFromAttributes({\n [ATTR_SERVICE_NAME]: resource.serviceName,\n [ATTR_SERVICE_VERSION]: resource.serviceVersion,\n }),\n processors: [logRecordProcessor],\n });\n\n // Register as global logger provider\n logs.setGlobalLoggerProvider(loggerProvider);\n\n const otelLogger = loggerProvider.getLogger(resource.serviceName, resource.serviceVersion);\n\n /**\n * Send a log to the collector\n */\n function sendLog(options: LogOptions): void {\n const { level, message, annotations = {} } = options;\n\n otelLogger.emit({\n severityNumber: severityMap[level],\n severityText: severityTextMap[level],\n body: message,\n attributes: annotations,\n });\n }\n\n /**\n * Force flush any pending logs (useful before page unload)\n */\n async function flushLogs(): Promise<void> {\n await loggerProvider.forceFlush();\n }\n\n return {\n debug: (message: string, annotations?: Record<string, string | number | boolean>) =>\n sendLog({ level: \"debug\", message, ...(annotations && { annotations }) }),\n info: (message: string, annotations?: Record<string, string | number | boolean>) =>\n sendLog({ level: \"info\", message, ...(annotations && { annotations }) }),\n warning: (message: string, annotations?: Record<string, string | number | boolean>) =>\n sendLog({ level: \"warning\", message, ...(annotations && { annotations }) }),\n error: (message: string, annotations?: Record<string, string | number | boolean>) =>\n sendLog({ level: \"error\", message, ...(annotations && { annotations }) }),\n flush: flushLogs,\n };\n}\n"],"mappings":"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","names":["BatchLogRecordProcessor","OTLPLogExporter"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@elto/telemetry",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "OpenTelemetry integration for Effect with Node.js and browser support",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"effect",
|
|
7
|
+
"logging",
|
|
8
|
+
"metrics",
|
|
9
|
+
"observability",
|
|
10
|
+
"opentelemetry",
|
|
11
|
+
"otel",
|
|
12
|
+
"tracing"
|
|
13
|
+
],
|
|
14
|
+
"license": "Apache-2.0",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "https://github.com/elto/telemetry.git"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist",
|
|
21
|
+
"LICENSE",
|
|
22
|
+
"README.md"
|
|
23
|
+
],
|
|
24
|
+
"type": "module",
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"import": {
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"default": "./dist/index.js"
|
|
30
|
+
},
|
|
31
|
+
"require": {
|
|
32
|
+
"types": "./dist/index.d.cts",
|
|
33
|
+
"default": "./dist/index.cjs"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"./node": {
|
|
37
|
+
"import": {
|
|
38
|
+
"types": "./dist/node/index.d.ts",
|
|
39
|
+
"default": "./dist/node/index.js"
|
|
40
|
+
},
|
|
41
|
+
"require": {
|
|
42
|
+
"types": "./dist/node/index.d.cts",
|
|
43
|
+
"default": "./dist/node/index.cjs"
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"./web": {
|
|
47
|
+
"import": {
|
|
48
|
+
"types": "./dist/web/index.d.ts",
|
|
49
|
+
"default": "./dist/web/index.js"
|
|
50
|
+
},
|
|
51
|
+
"require": {
|
|
52
|
+
"types": "./dist/web/index.d.cts",
|
|
53
|
+
"default": "./dist/web/index.cjs"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"publishConfig": {
|
|
58
|
+
"access": "public"
|
|
59
|
+
},
|
|
60
|
+
"scripts": {
|
|
61
|
+
"build": "tsup",
|
|
62
|
+
"dev": "tsup --watch",
|
|
63
|
+
"check-types": "tsc --noEmit",
|
|
64
|
+
"clean": "rm -rf dist"
|
|
65
|
+
},
|
|
66
|
+
"dependencies": {
|
|
67
|
+
"@effect/opentelemetry": "^0.61.0",
|
|
68
|
+
"@opentelemetry/api": "^1.9.0",
|
|
69
|
+
"@opentelemetry/api-logs": "^0.211.0",
|
|
70
|
+
"@opentelemetry/context-zone": "^2.5.0",
|
|
71
|
+
"@opentelemetry/exporter-logs-otlp-http": "^0.211.0",
|
|
72
|
+
"@opentelemetry/exporter-metrics-otlp-http": "^0.211.0",
|
|
73
|
+
"@opentelemetry/exporter-trace-otlp-http": "^0.211.0",
|
|
74
|
+
"@opentelemetry/resources": "^2.5.0",
|
|
75
|
+
"@opentelemetry/sdk-logs": "^0.211.0",
|
|
76
|
+
"@opentelemetry/sdk-metrics": "^2.5.0",
|
|
77
|
+
"@opentelemetry/sdk-trace-base": "^2.5.0",
|
|
78
|
+
"@opentelemetry/sdk-trace-node": "^2.5.0",
|
|
79
|
+
"@opentelemetry/sdk-trace-web": "^2.5.0",
|
|
80
|
+
"@opentelemetry/semantic-conventions": "^1.39.0"
|
|
81
|
+
},
|
|
82
|
+
"devDependencies": {
|
|
83
|
+
"@effect-rpc-otel/config": "workspace:*",
|
|
84
|
+
"@types/node": "catalog:",
|
|
85
|
+
"tsup": "^8.3.5",
|
|
86
|
+
"typescript": "catalog:"
|
|
87
|
+
},
|
|
88
|
+
"peerDependencies": {
|
|
89
|
+
"effect": "^3.0.0"
|
|
90
|
+
}
|
|
91
|
+
}
|