@kabran-tecnologia/kabran-config 1.7.0 → 1.8.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kabran-tecnologia/kabran-config",
3
- "version": "1.7.0",
3
+ "version": "1.8.0",
4
4
  "description": "Shared quality configurations, enforcement scripts, and CI/CD tooling for Kabran projects",
5
5
  "author": "Kabran",
6
6
  "license": "MIT",
@@ -27,6 +27,8 @@ NC='\033[0m'
27
27
  declare -a ERRORS=()
28
28
  declare -a STEP_RESULTS=()
29
29
  CI_START_TIME=""
30
+ CI_TRACE_ID=""
31
+ CI_SPAN_ID=""
30
32
 
31
33
  # ==============================================================================
32
34
  # Logging Functions
@@ -58,6 +60,128 @@ log_debug() {
58
60
  fi
59
61
  }
60
62
 
63
+ # ==============================================================================
64
+ # Trace Context Functions (OpenTelemetry W3C Trace Context)
65
+ # ==============================================================================
66
+
67
+ # Generate a random hex string of specified length
68
+ # Usage: generate_hex_string 32
69
+ generate_hex_string() {
70
+ local length="${1:-32}"
71
+ # Try multiple methods for generating random hex
72
+ if command -v openssl &>/dev/null; then
73
+ openssl rand -hex "$((length / 2))" 2>/dev/null
74
+ elif [ -r /dev/urandom ]; then
75
+ head -c "$((length / 2))" /dev/urandom | od -An -tx1 | tr -d ' \n' | head -c "$length"
76
+ else
77
+ # Fallback: use date + process ID + random
78
+ local seed="$$$(date +%s%N 2>/dev/null || date +%s)"
79
+ echo "$seed" | md5sum 2>/dev/null | head -c "$length" || echo "$seed" | head -c "$length"
80
+ fi
81
+ }
82
+
83
+ # Generate a W3C trace ID (32 hex chars = 128 bits)
84
+ # Usage: generate_trace_id
85
+ generate_trace_id() {
86
+ generate_hex_string 32
87
+ }
88
+
89
+ # Generate a W3C span ID (16 hex chars = 64 bits)
90
+ # Usage: generate_span_id
91
+ generate_span_id() {
92
+ generate_hex_string 16
93
+ }
94
+
95
+ # Initialize trace context for the CI run
96
+ # Sets TRACEPARENT env var if not already set
97
+ # Format: 00-{trace_id}-{span_id}-{flags}
98
+ # Usage: setup_trace_context
99
+ setup_trace_context() {
100
+ # Check if trace context already exists from environment
101
+ if [ -n "${TRACEPARENT:-}" ]; then
102
+ log_debug "Using existing TRACEPARENT: $TRACEPARENT"
103
+ # Extract trace_id and span_id from existing TRACEPARENT
104
+ CI_TRACE_ID=$(echo "$TRACEPARENT" | cut -d'-' -f2)
105
+ CI_SPAN_ID=$(echo "$TRACEPARENT" | cut -d'-' -f3)
106
+ return 0
107
+ fi
108
+
109
+ # Check for direct trace ID from environment
110
+ if [ -n "${OTEL_TRACE_ID:-}" ]; then
111
+ log_debug "Using OTEL_TRACE_ID: $OTEL_TRACE_ID"
112
+ CI_TRACE_ID="$OTEL_TRACE_ID"
113
+ CI_SPAN_ID=$(generate_span_id)
114
+ TRACEPARENT="00-${CI_TRACE_ID}-${CI_SPAN_ID}-01"
115
+ export TRACEPARENT
116
+ return 0
117
+ fi
118
+
119
+ # Check for GitHub Actions run ID (use as fallback correlation)
120
+ if [ -n "${GITHUB_RUN_ID:-}" ]; then
121
+ log_debug "Using GitHub run ID for trace correlation"
122
+ # Create deterministic trace_id from GitHub run info
123
+ local gh_seed="${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT:-1}"
124
+ CI_TRACE_ID=$(echo "$gh_seed" | md5sum | head -c 32)
125
+ CI_SPAN_ID=$(generate_span_id)
126
+ TRACEPARENT="00-${CI_TRACE_ID}-${CI_SPAN_ID}-01"
127
+ export TRACEPARENT
128
+ log_debug "Generated TRACEPARENT from GitHub: $TRACEPARENT"
129
+ return 0
130
+ fi
131
+
132
+ # Generate new trace context for local execution
133
+ log_debug "Generating new trace context for local CI run"
134
+ CI_TRACE_ID=$(generate_trace_id)
135
+ CI_SPAN_ID=$(generate_span_id)
136
+ TRACEPARENT="00-${CI_TRACE_ID}-${CI_SPAN_ID}-01"
137
+ export TRACEPARENT
138
+
139
+ log_info "Trace ID: ${CI_TRACE_ID:0:8}... (local)"
140
+ log_debug "Full TRACEPARENT: $TRACEPARENT"
141
+ return 0
142
+ }
143
+
144
+ # Get the current trace ID
145
+ # Usage: get_trace_id
146
+ get_trace_id() {
147
+ echo "${CI_TRACE_ID:-}"
148
+ }
149
+
150
+ # Get trace context info for metadata
151
+ # Usage: get_trace_context_json
152
+ get_trace_context_json() {
153
+ local trace_id="${CI_TRACE_ID:-}"
154
+ local span_id="${CI_SPAN_ID:-}"
155
+ local traceparent="${TRACEPARENT:-}"
156
+
157
+ if [ -z "$trace_id" ]; then
158
+ echo "null"
159
+ return
160
+ fi
161
+
162
+ # Determine source of trace context
163
+ local source="local"
164
+ if [ -n "${GITHUB_RUN_ID:-}" ]; then
165
+ source="github"
166
+ elif [ -n "${OTEL_TRACE_ID:-}" ]; then
167
+ source="otel_env"
168
+ elif [ -n "${TRACEPARENT:-}" ] && [ "${CI_TRACE_ID:-}" != "$(echo "$TRACEPARENT" | cut -d'-' -f2)" ]; then
169
+ source="external"
170
+ fi
171
+
172
+ jq -n \
173
+ --arg trace_id "$trace_id" \
174
+ --arg span_id "$span_id" \
175
+ --arg traceparent "$traceparent" \
176
+ --arg source "$source" \
177
+ '{
178
+ trace_id: $trace_id,
179
+ span_id: $span_id,
180
+ traceparent: $traceparent,
181
+ source: $source
182
+ }'
183
+ }
184
+
61
185
  # ==============================================================================
62
186
  # Version Compatibility Check
63
187
  # ==============================================================================
@@ -432,6 +556,10 @@ export_ci_data() {
432
556
  # Get scope
433
557
  local scope="${CI_SCOPE:-all}"
434
558
 
559
+ # Get trace context
560
+ local trace_context
561
+ trace_context=$(get_trace_context_json)
562
+
435
563
  # Generate intermediate data file for Node.js generator
436
564
  jq -n \
437
565
  --argjson steps "$steps_json" \
@@ -441,6 +569,7 @@ export_ci_data() {
441
569
  --arg finished_at "$now" \
442
570
  --arg project_name "$project_name" \
443
571
  --arg scope "$scope" \
572
+ --argjson trace_context "$trace_context" \
444
573
  '{
445
574
  steps: $steps,
446
575
  errors: $errors,
@@ -454,7 +583,8 @@ export_ci_data() {
454
583
  },
455
584
  metadata: {
456
585
  scope: $scope
457
- }
586
+ },
587
+ trace_context: $trace_context
458
588
  }' > "$output_file"
459
589
 
460
590
  log_debug "CI data exported to: $output_file"
@@ -171,6 +171,9 @@ if [ "$CI_SCOPE" != "all" ]; then
171
171
  fi
172
172
  echo ""
173
173
 
174
+ # Setup trace context (generates trace_id if not provided externally)
175
+ setup_trace_context
176
+
174
177
  # Start timing
175
178
  ci_start
176
179
 
@@ -177,8 +177,9 @@ export function generateCiResult(input) {
177
177
  // Determine exit code
178
178
  const exitCode = executionStats.steps_failed > 0 ? 1 : 0
179
179
 
180
- // Get trace ID if available
181
- const traceId = getTraceId()
180
+ // Get trace ID - prefer trace_context from input (shell-generated) over env vars
181
+ const traceContext = input.trace_context || {}
182
+ const traceId = traceContext.trace_id || getTraceId()
182
183
 
183
184
  // Build meta object
184
185
  const meta = {
@@ -198,7 +199,19 @@ export function generateCiResult(input) {
198
199
  // Build extensions with telemetry if trace_id exists
199
200
  const extensions = { ...(metadata.extensions || {}) }
200
201
  if (traceId) {
201
- extensions.telemetry = buildTelemetryExtension(traceId)
202
+ // Count errors from failed steps
203
+ const errorsRecorded = executionStats.steps_failed || 0
204
+
205
+ extensions.telemetry = buildTelemetryExtension(traceId, {
206
+ errorsRecorded,
207
+ // spans_exported remains 0 until we implement actual OTel export (GAP-004/Q12)
208
+ spansExported: 0,
209
+ })
210
+
211
+ // Add trace source info if available
212
+ if (traceContext.source) {
213
+ extensions.telemetry.trace_source = traceContext.source
214
+ }
202
215
  }
203
216
 
204
217
  // Build result object
@@ -0,0 +1,407 @@
1
+ # @kabran-tecnologia/kabran-config/telemetry
2
+
3
+ Unified telemetry package for Kabran projects using OpenTelemetry.
4
+
5
+ ## Features
6
+
7
+ - **Multi-runtime support**: Node.js, Frontend (Browser), Edge/Serverless
8
+ - **OpenTelemetry integration**: W3C Trace Context, OTLP export
9
+ - **Zero-config defaults**: Works out of the box with sensible defaults
10
+ - **Structured logging**: Logger with automatic trace correlation
11
+ - **Tree-shakeable**: Import only what you need
12
+
13
+ ## Installation
14
+
15
+ The telemetry modules are included in `@kabran-tecnologia/kabran-config`. Install the package and the required OpenTelemetry peer dependencies:
16
+
17
+ ```bash
18
+ # Install kabran-config
19
+ npm install @kabran-tecnologia/kabran-config
20
+
21
+ # Install required peer dependencies (pick based on your runtime)
22
+
23
+ # For Node.js:
24
+ npm install @opentelemetry/api @opentelemetry/sdk-trace-node @opentelemetry/exporter-trace-otlp-http @opentelemetry/resources @opentelemetry/semantic-conventions
25
+
26
+ # For Frontend:
27
+ npm install @opentelemetry/api @opentelemetry/sdk-trace-web @opentelemetry/exporter-trace-otlp-http @opentelemetry/resources @opentelemetry/semantic-conventions @opentelemetry/instrumentation-fetch @opentelemetry/instrumentation-document-load @opentelemetry/instrumentation-user-interaction
28
+
29
+ # For Edge/Serverless:
30
+ npm install @opentelemetry/api @opentelemetry/sdk-trace-base @opentelemetry/exporter-trace-otlp-http @opentelemetry/resources @opentelemetry/semantic-conventions
31
+ ```
32
+
33
+ ## Quick Start
34
+
35
+ ### Node.js (Express/Fastify)
36
+
37
+ ```javascript
38
+ // instrumentation.js - Import this FIRST in your app
39
+ import { initTelemetry, telemetryMiddleware, shutdownTelemetry } from '@kabran-tecnologia/kabran-config/telemetry/node'
40
+
41
+ // Initialize telemetry
42
+ initTelemetry({
43
+ serviceName: 'my-api',
44
+ serviceVersion: '1.0.0',
45
+ })
46
+
47
+ // In your Express app
48
+ import express from 'express'
49
+ const app = express()
50
+
51
+ // Add telemetry middleware (creates spans for each request)
52
+ app.use(telemetryMiddleware())
53
+
54
+ app.get('/api/users', (req, res) => {
55
+ // Your handler - automatically traced
56
+ res.json({ users: [] })
57
+ })
58
+
59
+ // Graceful shutdown
60
+ process.on('SIGTERM', async () => {
61
+ await shutdownTelemetry()
62
+ process.exit(0)
63
+ })
64
+ ```
65
+
66
+ ### Frontend (React/Vite)
67
+
68
+ ```typescript
69
+ // main.tsx
70
+ import { initTelemetry } from '@kabran-tecnologia/kabran-config/telemetry/frontend'
71
+
72
+ // Initialize before rendering
73
+ initTelemetry({
74
+ serviceName: 'my-frontend',
75
+ serviceVersion: '1.0.0',
76
+ // Optional: customize which events to trace
77
+ instrumentation: {
78
+ userInteractionEvents: ['click', 'submit'],
79
+ },
80
+ })
81
+
82
+ // Your React app renders normally
83
+ import { createRoot } from 'react-dom/client'
84
+ import App from './App'
85
+
86
+ createRoot(document.getElementById('root')!).render(<App />)
87
+ ```
88
+
89
+ ### Edge/Serverless (Supabase Functions)
90
+
91
+ ```typescript
92
+ // supabase/functions/my-function/index.ts
93
+ import { withTelemetry, traceSupabaseQuery } from '@kabran-tecnologia/kabran-config/telemetry/edge'
94
+ import { createClient } from '@supabase/supabase-js'
95
+
96
+ const supabase = createClient(
97
+ Deno.env.get('SUPABASE_URL')!,
98
+ Deno.env.get('SUPABASE_ANON_KEY')!
99
+ )
100
+
101
+ // Wrap your handler with telemetry
102
+ Deno.serve(withTelemetry(
103
+ {
104
+ serviceName: 'my-edge-function',
105
+ serviceVersion: '1.0.0',
106
+ },
107
+ async (req) => {
108
+ // Trace Supabase queries
109
+ const { data, error } = await traceSupabaseQuery(
110
+ 'select-users',
111
+ () => supabase.from('users').select('*')
112
+ )
113
+
114
+ return new Response(JSON.stringify({ data, error }), {
115
+ headers: { 'Content-Type': 'application/json' },
116
+ })
117
+ }
118
+ ))
119
+ ```
120
+
121
+ ## Structured Logging
122
+
123
+ The logger automatically includes trace context in log output:
124
+
125
+ ```javascript
126
+ import { createLogger } from '@kabran-tecnologia/kabran-config/telemetry/logger'
127
+
128
+ const log = createLogger()
129
+
130
+ log.info('User logged in', { userId: '123' })
131
+ // Output (JSON in production):
132
+ // {"level":"info","message":"User logged in","userId":"123","trace_id":"abc123...","span_id":"def456...","timestamp":"2024-01-13T..."}
133
+
134
+ // Output (pretty in development):
135
+ // 2024-01-13T12:00:00.000Z [INFO] User logged in [trace:abc123...] {"userId":"123"}
136
+ ```
137
+
138
+ ### Span-bound Logger
139
+
140
+ ```javascript
141
+ import { trace } from '@opentelemetry/api'
142
+ import { createSpanLogger } from '@kabran-tecnologia/kabran-config/telemetry/logger'
143
+
144
+ const span = trace.getActiveSpan()
145
+ const log = createSpanLogger(span)
146
+
147
+ log.info('Processing order', { orderId: '456' })
148
+ // Logs are also added as span events for visibility in traces
149
+ ```
150
+
151
+ ## Configuration
152
+
153
+ ### Environment Variables
154
+
155
+ All configuration can be set via environment variables:
156
+
157
+ ```bash
158
+ # Core
159
+ SERVICE_NAME=my-service # Required: Your service name
160
+ SERVICE_VERSION=1.0.0 # Service version (default: 1.0.0)
161
+ ENVIRONMENT=production # Environment name (default: from NODE_ENV)
162
+ OTEL_NAMESPACE=kabran # Service namespace (default: kabran)
163
+
164
+ # OTLP Exporter
165
+ OTEL_EXPORTER_OTLP_ENDPOINT=https://otel.kabran.com.br # Collector endpoint
166
+ OTEL_EXPORTER_OTLP_TIMEOUT=10000 # Export timeout in ms (default: 10000)
167
+
168
+ # Sampling
169
+ OTEL_SAMPLE_RATE=0.1 # Sampling rate 0.0-1.0 (default: 0.1 = 10%)
170
+
171
+ # Enable/Disable
172
+ OTEL_ENABLED=true # Enable telemetry (default: true in production)
173
+
174
+ # Logger
175
+ OTEL_LOG_TRACE_ID_LENGTH=8 # Trace ID length in logs (default: 8)
176
+ NO_COLOR=1 # Disable ANSI colors in logs
177
+ ```
178
+
179
+ ### Programmatic Configuration
180
+
181
+ ```javascript
182
+ import { initTelemetry } from '@kabran-tecnologia/kabran-config/telemetry/node'
183
+
184
+ initTelemetry({
185
+ // Required
186
+ serviceName: 'my-service',
187
+
188
+ // Optional (all have sensible defaults)
189
+ serviceVersion: '1.0.0',
190
+ environment: 'production',
191
+ endpoint: 'https://otel.kabran.com.br',
192
+ sampleRate: 0.1,
193
+ enabled: true,
194
+
195
+ // Resource attributes (added to all spans)
196
+ resourceAttributes: {
197
+ 'deployment.environment': 'production',
198
+ 'service.instance.id': process.env.POD_NAME,
199
+ },
200
+
201
+ // Batch processor settings (Node.js only)
202
+ batchProcessor: {
203
+ maxQueueSize: 2048,
204
+ maxExportBatchSize: 512,
205
+ scheduledDelayMillis: 5000,
206
+ },
207
+ })
208
+ ```
209
+
210
+ ## Module Reference
211
+
212
+ ### `telemetry/node`
213
+
214
+ For Node.js servers and long-running processes.
215
+
216
+ ```javascript
217
+ import {
218
+ initTelemetry, // Initialize the tracer
219
+ shutdownTelemetry, // Graceful shutdown
220
+ telemetryMiddleware,// Express/Fastify middleware
221
+ isInitialized, // Check if initialized
222
+ getTracer, // Get the tracer instance
223
+ getConfig, // Get resolved config
224
+ } from '@kabran-tecnologia/kabran-config/telemetry/node'
225
+ ```
226
+
227
+ ### `telemetry/frontend`
228
+
229
+ For browser applications (React, Vue, vanilla JS).
230
+
231
+ ```javascript
232
+ import {
233
+ initTelemetry, // Initialize the tracer
234
+ shutdownTelemetry, // Flush pending spans
235
+ isInitialized, // Check if initialized
236
+ getTracer, // Get the tracer instance
237
+ getConfig, // Get resolved config
238
+ } from '@kabran-tecnologia/kabran-config/telemetry/frontend'
239
+ ```
240
+
241
+ ### `telemetry/edge`
242
+
243
+ For Edge Functions and serverless (Supabase, Deno Deploy, Cloudflare Workers).
244
+
245
+ ```javascript
246
+ import {
247
+ withTelemetry, // Handler wrapper with auto-tracing
248
+ traceSupabaseQuery, // Trace Supabase queries
249
+ shutdownTelemetry, // Flush pending spans
250
+ isInitialized, // Check if initialized
251
+ getConfig, // Get resolved config
252
+ } from '@kabran-tecnologia/kabran-config/telemetry/edge'
253
+ ```
254
+
255
+ ### `telemetry/logger`
256
+
257
+ Structured logger with trace correlation.
258
+
259
+ ```javascript
260
+ import {
261
+ createLogger, // Create a logger instance
262
+ createSpanLogger, // Create a span-bound logger
263
+ log, // Default logger instance
264
+ getTraceContext, // Get current trace context
265
+ } from '@kabran-tecnologia/kabran-config/telemetry/logger'
266
+ ```
267
+
268
+ ### `telemetry/config`
269
+
270
+ Configuration utilities.
271
+
272
+ ```javascript
273
+ import {
274
+ defineTelemetryConfig, // Create a type-safe config
275
+ resolveConfig, // Resolve config with defaults and env vars
276
+ validateConfig, // Validate config object
277
+ detectEnabled, // Check if telemetry should be enabled
278
+ } from '@kabran-tecnologia/kabran-config/telemetry/config'
279
+ ```
280
+
281
+ ### `telemetry/shared`
282
+
283
+ Shared utilities and types.
284
+
285
+ ```javascript
286
+ import {
287
+ setAttributes, // Set multiple span attributes
288
+ formatDuration, // Format milliseconds to human-readable
289
+ generateInvocationId, // Generate unique invocation ID
290
+ safeWarn, // Safe console.warn
291
+ safeLog, // Safe console.log
292
+ } from '@kabran-tecnologia/kabran-config/telemetry/shared'
293
+ ```
294
+
295
+ ## Integration with Kosmos Observability
296
+
297
+ This package is designed to work with the Kosmos observability stack:
298
+
299
+ - **Traces** → Grafana Tempo
300
+ - **Logs** → Grafana Loki (via stdout/Promtail or direct export)
301
+ - **Metrics** → Prometheus (planned, see [GAP-006])
302
+
303
+ Default endpoint: `https://otel.kabran.com.br`
304
+
305
+ ### Viewing Traces
306
+
307
+ 1. Open Grafana at your Kosmos instance
308
+ 2. Go to Explore → Select Tempo
309
+ 3. Search by service name or trace ID
310
+
311
+ ## Best Practices
312
+
313
+ ### 1. Initialize Early
314
+
315
+ Initialize telemetry as early as possible in your application:
316
+
317
+ ```javascript
318
+ // This should be the FIRST import
319
+ import './instrumentation.js'
320
+
321
+ // Then your app code
322
+ import express from 'express'
323
+ ```
324
+
325
+ ### 2. Use Meaningful Span Names
326
+
327
+ ```javascript
328
+ // Good
329
+ span.updateName('user.create')
330
+ span.updateName('order.process')
331
+
332
+ // Bad
333
+ span.updateName('handler')
334
+ span.updateName('function1')
335
+ ```
336
+
337
+ ### 3. Add Relevant Attributes
338
+
339
+ ```javascript
340
+ import { trace } from '@opentelemetry/api'
341
+
342
+ const span = trace.getActiveSpan()
343
+ span?.setAttributes({
344
+ 'user.id': userId,
345
+ 'order.id': orderId,
346
+ 'order.total': total,
347
+ })
348
+ ```
349
+
350
+ ### 4. Handle Errors Properly
351
+
352
+ ```javascript
353
+ import { trace, SpanStatusCode } from '@opentelemetry/api'
354
+
355
+ try {
356
+ // Your code
357
+ } catch (error) {
358
+ const span = trace.getActiveSpan()
359
+ span?.recordException(error)
360
+ span?.setStatus({ code: SpanStatusCode.ERROR, message: error.message })
361
+ throw error
362
+ }
363
+ ```
364
+
365
+ ### 5. Graceful Shutdown
366
+
367
+ Always flush pending spans before shutdown:
368
+
369
+ ```javascript
370
+ process.on('SIGTERM', async () => {
371
+ await shutdownTelemetry()
372
+ process.exit(0)
373
+ })
374
+ ```
375
+
376
+ ## Troubleshooting
377
+
378
+ ### Traces not appearing
379
+
380
+ 1. Check `OTEL_ENABLED` is not set to `false`
381
+ 2. Verify `SERVICE_NAME` is set
382
+ 3. Check network connectivity to the collector endpoint
383
+ 4. Verify sampling rate (default is 10%)
384
+
385
+ ### Missing trace correlation in logs
386
+
387
+ 1. Ensure telemetry is initialized before logging
388
+ 2. Check that you're within an active span context
389
+
390
+ ### High memory usage
391
+
392
+ Reduce batch processor queue size:
393
+
394
+ ```javascript
395
+ initTelemetry({
396
+ serviceName: 'my-service',
397
+ batchProcessor: {
398
+ maxQueueSize: 512, // Lower from default 2048
399
+ },
400
+ })
401
+ ```
402
+
403
+ ## Related Documentation
404
+
405
+ - [OpenTelemetry JavaScript](https://opentelemetry.io/docs/languages/js/)
406
+ - [W3C Trace Context](https://www.w3.org/TR/trace-context/)
407
+ - [Kosmos Observability Stack](https://github.com/kabran-owner/kosmos/tree/main/services/observability)