@reactionary/source 0.0.28 → 0.0.29
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/core/package.json +2 -1
- package/core/src/providers/base.provider.ts +29 -9
- package/examples/trpc-node/.env.example +52 -0
- package/examples/trpc-node/src/main.ts +9 -1
- package/otel/README.md +248 -0
- package/otel/eslint.config.mjs +23 -0
- package/otel/package.json +20 -0
- package/otel/project.json +33 -0
- package/otel/src/config.ts +139 -0
- package/otel/src/index.ts +12 -0
- package/otel/src/metrics.ts +75 -0
- package/otel/src/provider-instrumentation.ts +107 -0
- package/otel/src/sdk.ts +119 -0
- package/otel/src/tracer.ts +82 -0
- package/otel/src/trpc-middleware.ts +127 -0
- package/otel/tsconfig.json +23 -0
- package/otel/tsconfig.lib.json +23 -0
- package/otel/tsconfig.spec.json +28 -0
- package/otel/vite.config.ts +24 -0
- package/package.json +15 -1
- package/project.json +14 -0
- package/trpc/package.json +1 -0
- package/trpc/src/index.ts +14 -4
- package/tsconfig.base.json +1 -0
- package/vitest.workspace.ts +4 -0
- package/examples/vue/eslint.config.mjs +0 -24
- package/examples/vue/index.html +0 -13
- package/examples/vue/project.json +0 -8
- package/examples/vue/src/app/App.vue +0 -275
- package/examples/vue/src/main.ts +0 -6
- package/examples/vue/src/styles.scss +0 -9
- package/examples/vue/src/vue-shims.d.ts +0 -5
- package/examples/vue/tsconfig.app.json +0 -14
- package/examples/vue/tsconfig.json +0 -20
- package/examples/vue/vite.config.ts +0 -31
package/core/package.json
CHANGED
|
@@ -3,6 +3,7 @@ import { Session } from '../schemas/session.schema';
|
|
|
3
3
|
import { BaseQuery } from '../schemas/queries/base.query';
|
|
4
4
|
import { BaseMutation } from '../schemas/mutations/base.mutation';
|
|
5
5
|
import { BaseModel } from '../schemas/models/base.model';
|
|
6
|
+
import { createProviderInstrumentation } from '@reactionary/otel';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Base capability provider, responsible for mutations (changes) and queries (fetches)
|
|
@@ -13,7 +14,11 @@ export abstract class BaseProvider<
|
|
|
13
14
|
Q extends BaseQuery = BaseQuery,
|
|
14
15
|
M extends BaseMutation = BaseMutation
|
|
15
16
|
> {
|
|
16
|
-
|
|
17
|
+
private instrumentation: ReturnType<typeof createProviderInstrumentation>;
|
|
18
|
+
|
|
19
|
+
constructor(public readonly schema: z.ZodType<T>, public readonly querySchema: z.ZodType<Q, Q>, public readonly mutationSchema: z.ZodType<M, M>) {
|
|
20
|
+
this.instrumentation = createProviderInstrumentation(this.constructor.name);
|
|
21
|
+
}
|
|
17
22
|
|
|
18
23
|
/**
|
|
19
24
|
* Validates that the final domain model constructed by the provider
|
|
@@ -37,13 +42,21 @@ export abstract class BaseProvider<
|
|
|
37
42
|
* of the results will match the order of the queries.
|
|
38
43
|
*/
|
|
39
44
|
public async query(queries: Q[], session: Session): Promise<T[]> {
|
|
40
|
-
|
|
45
|
+
return this.instrumentation.traceQuery(
|
|
46
|
+
'query',
|
|
47
|
+
async (span) => {
|
|
48
|
+
span.setAttribute('provider.query.count', queries.length);
|
|
49
|
+
const results = await this.fetch(queries, session);
|
|
41
50
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
51
|
+
for (const result of results) {
|
|
52
|
+
this.assert(result);
|
|
53
|
+
}
|
|
45
54
|
|
|
46
|
-
|
|
55
|
+
span.setAttribute('provider.result.count', results.length);
|
|
56
|
+
return results;
|
|
57
|
+
},
|
|
58
|
+
{ queryCount: queries.length }
|
|
59
|
+
);
|
|
47
60
|
}
|
|
48
61
|
|
|
49
62
|
/**
|
|
@@ -51,11 +64,18 @@ export abstract class BaseProvider<
|
|
|
51
64
|
* resulting from that set of operations.
|
|
52
65
|
*/
|
|
53
66
|
public async mutate(mutations: M[], session: Session): Promise<T> {
|
|
54
|
-
|
|
67
|
+
return this.instrumentation.traceMutation(
|
|
68
|
+
'mutate',
|
|
69
|
+
async (span) => {
|
|
70
|
+
span.setAttribute('provider.mutation.count', mutations.length);
|
|
71
|
+
const result = await this.process(mutations, session);
|
|
55
72
|
|
|
56
|
-
|
|
73
|
+
this.assert(result);
|
|
57
74
|
|
|
58
|
-
|
|
75
|
+
return result;
|
|
76
|
+
},
|
|
77
|
+
{ mutationCount: mutations.length }
|
|
78
|
+
);
|
|
59
79
|
}
|
|
60
80
|
|
|
61
81
|
/**
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# OpenTelemetry Configuration (Standard Environment Variables)
|
|
2
|
+
# See: https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/
|
|
3
|
+
# ============================
|
|
4
|
+
|
|
5
|
+
# Service identification
|
|
6
|
+
OTEL_SERVICE_NAME=reactionary-trpc-example
|
|
7
|
+
OTEL_SERVICE_VERSION=1.0.0
|
|
8
|
+
DEPLOYMENT_ENVIRONMENT=development
|
|
9
|
+
|
|
10
|
+
# Traces exporter: console | otlp | otlp/http | none
|
|
11
|
+
OTEL_TRACES_EXPORTER=console
|
|
12
|
+
|
|
13
|
+
# Metrics exporter: console | otlp | otlp/http | none
|
|
14
|
+
OTEL_METRICS_EXPORTER=console
|
|
15
|
+
|
|
16
|
+
# For OTLP exporters (e.g., Honeycomb, Jaeger, Grafana)
|
|
17
|
+
# OTEL_EXPORTER_OTLP_ENDPOINT=https://api.honeycomb.io
|
|
18
|
+
# OTEL_EXPORTER_OTLP_HEADERS=x-honeycomb-team=your-api-key
|
|
19
|
+
|
|
20
|
+
# Or use specific endpoints for traces/metrics
|
|
21
|
+
# OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://api.honeycomb.io/v1/traces
|
|
22
|
+
# OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=https://api.honeycomb.io/v1/metrics
|
|
23
|
+
|
|
24
|
+
# For local Jaeger
|
|
25
|
+
# OTEL_TRACES_EXPORTER=otlp
|
|
26
|
+
# OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
|
|
27
|
+
|
|
28
|
+
# Debug logging
|
|
29
|
+
# OTEL_LOG_LEVEL=debug
|
|
30
|
+
|
|
31
|
+
# Metrics export interval (milliseconds)
|
|
32
|
+
OTEL_METRIC_EXPORT_INTERVAL=60000
|
|
33
|
+
|
|
34
|
+
# Session Store Configuration
|
|
35
|
+
# ============================
|
|
36
|
+
SESSION_STORE_REDIS_CONNECTION=redis://localhost:6379
|
|
37
|
+
SESSION_STORE_SECRET=your-session-secret-here
|
|
38
|
+
|
|
39
|
+
# Provider API Keys
|
|
40
|
+
# ==================
|
|
41
|
+
ALGOLIA_API_KEY=your-algolia-api-key
|
|
42
|
+
ALGOLIA_APP_ID=your-algolia-app-id
|
|
43
|
+
ALGOLIA_INDEX=your-algolia-index
|
|
44
|
+
|
|
45
|
+
COMMERCETOOLS_API_URL=https://api.commercetools.com
|
|
46
|
+
COMMERCETOOLS_AUTH_URL=https://auth.commercetools.com
|
|
47
|
+
COMMERCETOOLS_CLIENT_ID=your-client-id
|
|
48
|
+
COMMERCETOOLS_CLIENT_SECRET=your-client-secret
|
|
49
|
+
COMMERCETOOLS_PROJECT_KEY=your-project-key
|
|
50
|
+
|
|
51
|
+
POSTHOG_API_KEY=your-posthog-api-key
|
|
52
|
+
POSTHOG_HOST=https://app.posthog.com
|
|
@@ -52,4 +52,12 @@ app.use(
|
|
|
52
52
|
})
|
|
53
53
|
);
|
|
54
54
|
|
|
55
|
-
app.listen(3000)
|
|
55
|
+
const server = app.listen(3000, () => {
|
|
56
|
+
console.log('Server started on http://localhost:3000');
|
|
57
|
+
// OTEL auto-initializes based on standard env vars
|
|
58
|
+
if (process.env['OTEL_LOG_LEVEL'] === 'debug') {
|
|
59
|
+
console.log('OTEL traces exporter:', process.env['OTEL_TRACES_EXPORTER'] || 'console');
|
|
60
|
+
console.log('OTEL metrics exporter:', process.env['OTEL_METRICS_EXPORTER'] || 'console');
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
package/otel/README.md
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
# @reactionary/otel
|
|
2
|
+
|
|
3
|
+
Zero-configuration OpenTelemetry instrumentation for Reactionary framework. Automatically instruments tRPC routes and providers using standard OTEL environment variables.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Zero Configuration**: Auto-initializes on first use with standard OTEL env vars
|
|
8
|
+
- **Automatic tRPC Route Tracing**: All tRPC procedures automatically traced
|
|
9
|
+
- **Provider Instrumentation**: BaseProvider operations automatically instrumented
|
|
10
|
+
- **Standard Compliance**: Uses official OpenTelemetry environment variables
|
|
11
|
+
- **Multiple Exporters**: Console, OTLP/HTTP, or custom exporters
|
|
12
|
+
- **Metrics Collection**: Request counts, durations, errors automatically tracked
|
|
13
|
+
- **Lazy Initialization**: Only starts when actually used
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pnpm add @reactionary/otel
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
That's it! No initialization code needed.
|
|
22
|
+
|
|
23
|
+
## How It Works
|
|
24
|
+
|
|
25
|
+
The OTEL package automatically initializes itself on first use, reading configuration from standard OpenTelemetry environment variables. When your code first creates a span or metric, the SDK initializes with your environment configuration.
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
// No imports or initialization needed!
|
|
29
|
+
// Just use your tRPC router or providers normally
|
|
30
|
+
import { createTRPCRouter } from '@reactionary/trpc';
|
|
31
|
+
|
|
32
|
+
const router = createTRPCRouter(client);
|
|
33
|
+
// ↑ Automatically instrumented when OTEL env vars are set
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Configuration
|
|
37
|
+
|
|
38
|
+
Use standard OpenTelemetry environment variables. No code changes needed.
|
|
39
|
+
|
|
40
|
+
### Standard Environment Variables
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# Service identification
|
|
44
|
+
OTEL_SERVICE_NAME=my-service
|
|
45
|
+
OTEL_SERVICE_VERSION=1.0.0
|
|
46
|
+
DEPLOYMENT_ENVIRONMENT=production
|
|
47
|
+
|
|
48
|
+
# Traces exporter (console | otlp | otlp/http | none)
|
|
49
|
+
OTEL_TRACES_EXPORTER=otlp
|
|
50
|
+
|
|
51
|
+
# Metrics exporter (console | otlp | otlp/http | none)
|
|
52
|
+
OTEL_METRICS_EXPORTER=otlp
|
|
53
|
+
|
|
54
|
+
# OTLP endpoint and headers
|
|
55
|
+
OTEL_EXPORTER_OTLP_ENDPOINT=https://api.honeycomb.io
|
|
56
|
+
OTEL_EXPORTER_OTLP_HEADERS=x-honeycomb-team=your-api-key
|
|
57
|
+
|
|
58
|
+
# Or use specific endpoints
|
|
59
|
+
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://api.honeycomb.io/v1/traces
|
|
60
|
+
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=https://api.honeycomb.io/v1/metrics
|
|
61
|
+
|
|
62
|
+
# Debug logging
|
|
63
|
+
OTEL_LOG_LEVEL=debug
|
|
64
|
+
|
|
65
|
+
# Metrics export interval (milliseconds)
|
|
66
|
+
OTEL_METRIC_EXPORT_INTERVAL=60000
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
See the [OpenTelemetry specification](https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/) for all available options.
|
|
70
|
+
|
|
71
|
+
## Exporters
|
|
72
|
+
|
|
73
|
+
### Console (Development)
|
|
74
|
+
```bash
|
|
75
|
+
OTEL_TRACES_EXPORTER=console
|
|
76
|
+
OTEL_METRICS_EXPORTER=console
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### OTLP (Production)
|
|
80
|
+
Works with any OTLP-compatible backend:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
OTEL_TRACES_EXPORTER=otlp
|
|
84
|
+
OTEL_METRICS_EXPORTER=otlp
|
|
85
|
+
OTEL_EXPORTER_OTLP_ENDPOINT=https://api.honeycomb.io
|
|
86
|
+
OTEL_EXPORTER_OTLP_HEADERS=x-honeycomb-team=your-api-key
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Disable
|
|
90
|
+
```bash
|
|
91
|
+
OTEL_TRACES_EXPORTER=none
|
|
92
|
+
OTEL_METRICS_EXPORTER=none
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Custom Instrumentation
|
|
96
|
+
|
|
97
|
+
### Manual Spans
|
|
98
|
+
|
|
99
|
+
Create custom spans for specific operations:
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
import { withSpan, getTracer } from '@reactionary/otel';
|
|
103
|
+
|
|
104
|
+
// Using withSpan helper
|
|
105
|
+
const result = await withSpan('custom-operation', async (span) => {
|
|
106
|
+
span.setAttribute('custom.attribute', 'value');
|
|
107
|
+
// Your operation here
|
|
108
|
+
return someAsyncOperation();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Using tracer directly
|
|
112
|
+
const tracer = getTracer();
|
|
113
|
+
const span = tracer.startSpan('manual-span');
|
|
114
|
+
try {
|
|
115
|
+
// Your operation
|
|
116
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
117
|
+
} catch (error) {
|
|
118
|
+
span.recordException(error);
|
|
119
|
+
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
120
|
+
throw error;
|
|
121
|
+
} finally {
|
|
122
|
+
span.end();
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Provider Instrumentation
|
|
127
|
+
|
|
128
|
+
Providers are automatically instrumented when extending BaseProvider:
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
import { BaseProvider } from '@reactionary/core';
|
|
132
|
+
|
|
133
|
+
class MyProvider extends BaseProvider {
|
|
134
|
+
// Automatically traced when OTEL is initialized
|
|
135
|
+
protected async fetch(queries, session) {
|
|
136
|
+
// Your implementation
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
protected async process(mutations, session) {
|
|
140
|
+
// Your implementation
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Custom Metrics
|
|
146
|
+
|
|
147
|
+
Track custom business metrics:
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
import { getMetrics } from '@reactionary/otel';
|
|
151
|
+
|
|
152
|
+
const metrics = getMetrics();
|
|
153
|
+
|
|
154
|
+
// Increment counter
|
|
155
|
+
metrics.requestCounter.add(1, {
|
|
156
|
+
'endpoint': '/api/users',
|
|
157
|
+
'method': 'GET'
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// Record histogram
|
|
161
|
+
metrics.requestDuration.record(150, {
|
|
162
|
+
'endpoint': '/api/users',
|
|
163
|
+
'status': 'success'
|
|
164
|
+
});
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Examples
|
|
168
|
+
|
|
169
|
+
### Honeycomb
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
OTEL_SERVICE_NAME=my-service
|
|
173
|
+
OTEL_TRACES_EXPORTER=otlp
|
|
174
|
+
OTEL_EXPORTER_OTLP_ENDPOINT=https://api.honeycomb.io
|
|
175
|
+
OTEL_EXPORTER_OTLP_HEADERS=x-honeycomb-team=your-api-key
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Local Development
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
OTEL_SERVICE_NAME=my-service-dev
|
|
182
|
+
OTEL_TRACES_EXPORTER=console
|
|
183
|
+
OTEL_METRICS_EXPORTER=none # Disable metrics in dev
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Docker Compose with Jaeger
|
|
187
|
+
|
|
188
|
+
```yaml
|
|
189
|
+
services:
|
|
190
|
+
app:
|
|
191
|
+
environment:
|
|
192
|
+
- OTEL_EXPORTER_TYPE=otlp
|
|
193
|
+
- OTEL_COLLECTOR_ENDPOINT=http://jaeger:4318
|
|
194
|
+
- OTEL_SERVICE_NAME=my-service
|
|
195
|
+
|
|
196
|
+
jaeger:
|
|
197
|
+
image: jaegertracing/all-in-one:latest
|
|
198
|
+
ports:
|
|
199
|
+
- "16686:16686" # Jaeger UI
|
|
200
|
+
- "4318:4318" # OTLP HTTP
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Metrics Reference
|
|
204
|
+
|
|
205
|
+
The following metrics are automatically collected:
|
|
206
|
+
|
|
207
|
+
| Metric | Type | Description |
|
|
208
|
+
|--------|------|-------------|
|
|
209
|
+
| `reactionary.requests` | Counter | Total number of requests |
|
|
210
|
+
| `reactionary.request.duration` | Histogram | Request duration in milliseconds |
|
|
211
|
+
| `reactionary.requests.active` | UpDownCounter | Number of active requests |
|
|
212
|
+
| `reactionary.errors` | Counter | Total number of errors |
|
|
213
|
+
| `reactionary.provider.calls` | Counter | Total provider calls |
|
|
214
|
+
| `reactionary.provider.duration` | Histogram | Provider call duration |
|
|
215
|
+
| `reactionary.cache.hits` | Counter | Cache hit count |
|
|
216
|
+
| `reactionary.cache.misses` | Counter | Cache miss count |
|
|
217
|
+
|
|
218
|
+
## Best Practices
|
|
219
|
+
|
|
220
|
+
1. **Use Standard Variables**: Stick to OpenTelemetry standard environment variables
|
|
221
|
+
2. **Set Service Name**: Always set `OTEL_SERVICE_NAME` for service identification
|
|
222
|
+
3. **Environment-based Config**: Use different configs for dev/staging/production
|
|
223
|
+
4. **Add Context**: Use span attributes to add business context to traces
|
|
224
|
+
5. **Handle Errors**: Ensure spans are properly closed even on errors
|
|
225
|
+
6. **Sample Wisely**: Consider sampling strategies for high-volume services
|
|
226
|
+
7. **Monitor Performance**: Watch for overhead in high-throughput scenarios
|
|
227
|
+
|
|
228
|
+
## Troubleshooting
|
|
229
|
+
|
|
230
|
+
### Traces Not Appearing
|
|
231
|
+
|
|
232
|
+
1. Check OTEL is initialized before other components
|
|
233
|
+
2. Verify `OTEL_TRACE_ENABLED` is not set to `false`
|
|
234
|
+
3. Check exporter configuration and endpoint connectivity
|
|
235
|
+
4. Look for initialization errors in console
|
|
236
|
+
|
|
237
|
+
### Performance Impact
|
|
238
|
+
|
|
239
|
+
- Use sampling to reduce overhead
|
|
240
|
+
- Disable metrics if not needed
|
|
241
|
+
- Consider using batch exporters
|
|
242
|
+
- Increase export intervals for metrics
|
|
243
|
+
|
|
244
|
+
### Memory Usage
|
|
245
|
+
|
|
246
|
+
- Monitor span processor queue size
|
|
247
|
+
- Adjust batch size and timeout
|
|
248
|
+
- Consider using sampling for high-volume services
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import baseConfig from '../eslint.config.mjs';
|
|
2
|
+
|
|
3
|
+
export default [
|
|
4
|
+
...baseConfig,
|
|
5
|
+
{
|
|
6
|
+
files: ['**/*.json'],
|
|
7
|
+
rules: {
|
|
8
|
+
'@nx/dependency-checks': [
|
|
9
|
+
'error',
|
|
10
|
+
{
|
|
11
|
+
ignoredFiles: [
|
|
12
|
+
'{projectRoot}/eslint.config.{js,cjs,mjs}',
|
|
13
|
+
'{projectRoot}/esbuild.config.{js,ts,mjs,mts}',
|
|
14
|
+
'{projectRoot}/vite.config.{js,ts,mjs,mts}',
|
|
15
|
+
],
|
|
16
|
+
},
|
|
17
|
+
],
|
|
18
|
+
},
|
|
19
|
+
languageOptions: {
|
|
20
|
+
parser: await import('jsonc-eslint-parser'),
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
];
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@reactionary/otel",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "commonjs",
|
|
5
|
+
"main": "./src/index.js",
|
|
6
|
+
"types": "./src/index.d.ts",
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"@opentelemetry/api": "^1.9.0",
|
|
9
|
+
"@opentelemetry/sdk-node": "^0.203.0",
|
|
10
|
+
"@opentelemetry/sdk-trace-base": "^2.0.1",
|
|
11
|
+
"@opentelemetry/sdk-metrics": "^2.0.1",
|
|
12
|
+
"@opentelemetry/resources": "^2.0.1",
|
|
13
|
+
"@opentelemetry/semantic-conventions": "^1.36.0",
|
|
14
|
+
"@opentelemetry/exporter-trace-otlp-http": "^0.203.0",
|
|
15
|
+
"@opentelemetry/exporter-metrics-otlp-http": "^0.203.0",
|
|
16
|
+
"@opentelemetry/instrumentation-http": "^0.203.0",
|
|
17
|
+
"@opentelemetry/instrumentation-express": "^0.52.0",
|
|
18
|
+
"@trpc/server": "^11.1.2"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "otel",
|
|
3
|
+
"$schema": "../node_modules/nx/schemas/project-schema.json",
|
|
4
|
+
"sourceRoot": "otel/src",
|
|
5
|
+
"projectType": "library",
|
|
6
|
+
"release": {
|
|
7
|
+
"version": {
|
|
8
|
+
"manifestRootsToUpdate": ["dist/{projectRoot}"],
|
|
9
|
+
"currentVersionResolver": "git-tag",
|
|
10
|
+
"fallbackCurrentVersionResolver": "disk"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"tags": [],
|
|
14
|
+
"targets": {
|
|
15
|
+
"build": {
|
|
16
|
+
"executor": "@nx/esbuild:esbuild",
|
|
17
|
+
"outputs": ["{options.outputPath}"],
|
|
18
|
+
"options": {
|
|
19
|
+
"outputPath": "dist/otel",
|
|
20
|
+
"main": "otel/src/index.ts",
|
|
21
|
+
"tsConfig": "otel/tsconfig.lib.json",
|
|
22
|
+
"assets": ["otel/*.md"],
|
|
23
|
+
"format": ["cjs"],
|
|
24
|
+
"generatePackageJson": true
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"nx-release-publish": {
|
|
28
|
+
"options": {
|
|
29
|
+
"packageRoot": "dist/{projectRoot}"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { SpanExporter } from '@opentelemetry/sdk-trace-base';
|
|
2
|
+
import { ConsoleSpanExporter } from '@opentelemetry/sdk-trace-base';
|
|
3
|
+
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
|
|
4
|
+
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http';
|
|
5
|
+
import { MetricReader, PeriodicExportingMetricReader, ConsoleMetricExporter } from '@opentelemetry/sdk-metrics';
|
|
6
|
+
|
|
7
|
+
// Internal config interface - not exported
|
|
8
|
+
interface OtelConfig {
|
|
9
|
+
serviceName: string;
|
|
10
|
+
serviceVersion?: string;
|
|
11
|
+
environment?: string;
|
|
12
|
+
otlpEndpoint?: string;
|
|
13
|
+
otlpHeaders?: Record<string, string>;
|
|
14
|
+
traceEnabled: boolean;
|
|
15
|
+
metricsEnabled: boolean;
|
|
16
|
+
metricExportIntervalMillis: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Detect if we're running in a browser environment
|
|
20
|
+
function isBrowser(): boolean {
|
|
21
|
+
return typeof window !== 'undefined' && typeof process === 'undefined';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Parse config from standard OTEL environment variables
|
|
25
|
+
// https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/
|
|
26
|
+
export function getConfigFromEnv(): OtelConfig {
|
|
27
|
+
// In browser environments, return disabled config
|
|
28
|
+
if (isBrowser()) {
|
|
29
|
+
return {
|
|
30
|
+
serviceName: 'browser-service',
|
|
31
|
+
serviceVersion: undefined,
|
|
32
|
+
environment: 'browser',
|
|
33
|
+
otlpEndpoint: undefined,
|
|
34
|
+
otlpHeaders: undefined,
|
|
35
|
+
traceEnabled: false,
|
|
36
|
+
metricsEnabled: false,
|
|
37
|
+
metricExportIntervalMillis: 60000,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Determine exporter type from standard OTEL vars
|
|
42
|
+
const tracesExporter = process.env['OTEL_TRACES_EXPORTER'] || 'console';
|
|
43
|
+
const metricsExporter = process.env['OTEL_METRICS_EXPORTER'] || 'console';
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
serviceName: process.env['OTEL_SERVICE_NAME'] || 'unknown_service',
|
|
47
|
+
serviceVersion: process.env['OTEL_SERVICE_VERSION'],
|
|
48
|
+
environment: process.env['DEPLOYMENT_ENVIRONMENT'] || process.env['NODE_ENV'] || 'development',
|
|
49
|
+
otlpEndpoint: process.env['OTEL_EXPORTER_OTLP_ENDPOINT'],
|
|
50
|
+
otlpHeaders: process.env['OTEL_EXPORTER_OTLP_HEADERS']
|
|
51
|
+
? parseHeaders(process.env['OTEL_EXPORTER_OTLP_HEADERS'])
|
|
52
|
+
: undefined,
|
|
53
|
+
traceEnabled: tracesExporter !== 'none',
|
|
54
|
+
metricsEnabled: metricsExporter !== 'none',
|
|
55
|
+
metricExportIntervalMillis: process.env['OTEL_METRIC_EXPORT_INTERVAL']
|
|
56
|
+
? parseInt(process.env['OTEL_METRIC_EXPORT_INTERVAL'], 10)
|
|
57
|
+
: 60000, // Default 60 seconds per OTEL spec
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function parseHeaders(headerString: string): Record<string, string> {
|
|
62
|
+
const headers: Record<string, string> = {};
|
|
63
|
+
headerString.split(',').forEach(header => {
|
|
64
|
+
const [key, value] = header.split('=');
|
|
65
|
+
if (key && value) {
|
|
66
|
+
headers[key.trim()] = value.trim();
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
return headers;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function createTraceExporter(config: OtelConfig): SpanExporter | undefined {
|
|
73
|
+
if (!config.traceEnabled) {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const tracesExporter = process.env['OTEL_TRACES_EXPORTER'] || 'console';
|
|
78
|
+
|
|
79
|
+
switch (tracesExporter) {
|
|
80
|
+
case 'otlp':
|
|
81
|
+
case 'otlp/http': {
|
|
82
|
+
if (!config.otlpEndpoint) {
|
|
83
|
+
console.warn('OTEL_EXPORTER_OTLP_ENDPOINT not set, falling back to console');
|
|
84
|
+
return new ConsoleSpanExporter();
|
|
85
|
+
}
|
|
86
|
+
// Use standard endpoint resolution
|
|
87
|
+
const tracesEndpoint = process.env['OTEL_EXPORTER_OTLP_TRACES_ENDPOINT'] ||
|
|
88
|
+
`${config.otlpEndpoint}/v1/traces`;
|
|
89
|
+
return new OTLPTraceExporter({
|
|
90
|
+
url: tracesEndpoint,
|
|
91
|
+
headers: config.otlpHeaders,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
case 'console':
|
|
95
|
+
return new ConsoleSpanExporter();
|
|
96
|
+
case 'none':
|
|
97
|
+
default:
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function createMetricReader(config: OtelConfig): MetricReader | undefined {
|
|
103
|
+
if (!config.metricsEnabled) {
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const metricsExporter = process.env['OTEL_METRICS_EXPORTER'] || 'console';
|
|
108
|
+
let exporter;
|
|
109
|
+
|
|
110
|
+
switch (metricsExporter) {
|
|
111
|
+
case 'otlp':
|
|
112
|
+
case 'otlp/http': {
|
|
113
|
+
if (!config.otlpEndpoint) {
|
|
114
|
+
console.warn('OTEL_EXPORTER_OTLP_ENDPOINT not set, falling back to console');
|
|
115
|
+
exporter = new ConsoleMetricExporter();
|
|
116
|
+
} else {
|
|
117
|
+
// Use standard endpoint resolution
|
|
118
|
+
const metricsEndpoint = process.env['OTEL_EXPORTER_OTLP_METRICS_ENDPOINT'] ||
|
|
119
|
+
`${config.otlpEndpoint}/v1/metrics`;
|
|
120
|
+
exporter = new OTLPMetricExporter({
|
|
121
|
+
url: metricsEndpoint,
|
|
122
|
+
headers: config.otlpHeaders,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
case 'console':
|
|
128
|
+
exporter = new ConsoleMetricExporter();
|
|
129
|
+
break;
|
|
130
|
+
case 'none':
|
|
131
|
+
default:
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return new PeriodicExportingMetricReader({
|
|
136
|
+
exporter,
|
|
137
|
+
exportIntervalMillis: config.metricExportIntervalMillis,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// SDK functions - auto-initialization handled internally
|
|
2
|
+
export { isOtelInitialized, shutdownOtel } from './sdk';
|
|
3
|
+
|
|
4
|
+
// Tracing utilities
|
|
5
|
+
export * from './tracer';
|
|
6
|
+
export * from './metrics';
|
|
7
|
+
export * from './trpc-middleware';
|
|
8
|
+
export * from './provider-instrumentation';
|
|
9
|
+
|
|
10
|
+
// Re-export common OTEL types
|
|
11
|
+
export { trace, context, SpanKind, SpanStatusCode } from '@opentelemetry/api';
|
|
12
|
+
export type { Span, Tracer, SpanOptions, Attributes } from '@opentelemetry/api';
|