@payloops/observability 0.0.1 → 0.0.5

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,9 +1,16 @@
1
1
  {
2
2
  "name": "@payloops/observability",
3
- "version": "0.0.1",
3
+ "version": "0.0.5",
4
4
  "type": "module",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/payloops/observability"
8
+ },
5
9
  "main": "dist/index.js",
6
10
  "types": "dist/index.d.ts",
11
+ "files": [
12
+ "dist"
13
+ ],
7
14
  "exports": {
8
15
  ".": {
9
16
  "import": "./dist/index.js",
@@ -13,7 +20,10 @@
13
20
  "scripts": {
14
21
  "build": "tsup src/index.ts --format esm --dts",
15
22
  "lint": "eslint src/",
16
- "typecheck": "tsc --noEmit"
23
+ "typecheck": "tsc --noEmit",
24
+ "release": "npm run build && npm run typecheck && npm version patch && git push && git push --tags",
25
+ "release:minor": "npm run build && npm run typecheck && npm version minor && git push && git push --tags",
26
+ "release:major": "npm run build && npm run typecheck && npm version major && git push && git push --tags"
17
27
  },
18
28
  "dependencies": {
19
29
  "@opentelemetry/api": "^1.9.0",
package/.env.example DELETED
@@ -1,4 +0,0 @@
1
- # OpenTelemetry
2
- OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
3
- OTEL_SERVICE_NAME=loop-service
4
- NODE_ENV=development
@@ -1,38 +0,0 @@
1
- name: CI
2
-
3
- on:
4
- push:
5
- branches: [main]
6
- pull_request:
7
- branches: [main]
8
-
9
- jobs:
10
- build:
11
- runs-on: ubuntu-latest
12
-
13
- steps:
14
- - uses: actions/checkout@v4
15
-
16
- - uses: pnpm/action-setup@v4
17
- with:
18
- version: 9
19
-
20
- - uses: actions/setup-node@v4
21
- with:
22
- node-version: '22'
23
- cache: 'pnpm'
24
-
25
- - name: Update npm to latest
26
- run: npm install -g npm@latest
27
-
28
- - name: Install dependencies
29
- run: pnpm install
30
-
31
- - name: Type check
32
- run: pnpm typecheck
33
-
34
- - name: Lint
35
- run: pnpm lint
36
-
37
- - name: Build
38
- run: pnpm build
@@ -1,40 +0,0 @@
1
- name: Publish to npm
2
-
3
- on:
4
- release:
5
- types: [created]
6
-
7
- jobs:
8
- publish:
9
- runs-on: ubuntu-latest
10
-
11
- permissions:
12
- contents: read
13
- id-token: write
14
-
15
- steps:
16
- - uses: actions/checkout@v4
17
-
18
- - uses: pnpm/action-setup@v4
19
- with:
20
- version: 9
21
-
22
- - uses: actions/setup-node@v4
23
- with:
24
- node-version: '22'
25
- cache: 'pnpm'
26
- registry-url: 'https://registry.npmjs.org'
27
-
28
- - name: Update npm to latest
29
- run: npm install -g npm@latest
30
-
31
- - name: Install dependencies
32
- run: pnpm install
33
-
34
- - name: Build
35
- run: pnpm build
36
-
37
- - name: Publish to npm with provenance
38
- run: npm publish --access public --provenance
39
- env:
40
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
package/src/index.ts DELETED
@@ -1,56 +0,0 @@
1
- // OpenTelemetry
2
- export { initTelemetry, shutdownTelemetry, type TelemetryConfig } from './lib/otel';
3
-
4
- // Logger
5
- export { logger, createActivityLogger, createWorkflowLogger, createRequestLogger } from './lib/logger';
6
-
7
- // Correlation Context
8
- export {
9
- getCorrelationContext,
10
- withCorrelationContext,
11
- withCorrelationContextAsync,
12
- generateCorrelationId,
13
- extractCorrelationId,
14
- createPropagationHeaders,
15
- createContextFromMemo,
16
- CORRELATION_ID_HEADER,
17
- REQUEST_ID_HEADER,
18
- type CorrelationContext
19
- } from './lib/context';
20
-
21
- // Metrics
22
- export {
23
- // Payment metrics
24
- paymentCounter,
25
- paymentAmountHistogram,
26
- paymentLatencyHistogram,
27
- recordPaymentAttempt,
28
- recordPaymentAmount,
29
- recordPaymentLatency,
30
-
31
- // Webhook metrics
32
- webhookDeliveryCounter,
33
- webhookLatencyHistogram,
34
- recordWebhookDelivery,
35
- recordWebhookLatency,
36
-
37
- // HTTP metrics
38
- httpRequestCounter,
39
- httpRequestLatencyHistogram,
40
- activeRequestsGauge,
41
- recordHttpRequest,
42
-
43
- // Database metrics
44
- dbQueryHistogram,
45
- dbConnectionGauge,
46
-
47
- // Workflow metrics
48
- workflowStartedCounter,
49
- workflowCompletedCounter,
50
- workflowFailedCounter,
51
- activityLatencyHistogram,
52
- recordWorkflowStarted,
53
- recordWorkflowCompleted,
54
- recordWorkflowFailed,
55
- recordActivityLatency
56
- } from './lib/metrics';
@@ -1,90 +0,0 @@
1
- import { AsyncLocalStorage } from 'async_hooks';
2
- import { nanoid } from 'nanoid';
3
- import { context, propagation } from '@opentelemetry/api';
4
-
5
- /**
6
- * Correlation context for tracking requests across services
7
- */
8
- export interface CorrelationContext {
9
- correlationId: string;
10
- merchantId?: string;
11
- orderId?: string;
12
- workflowId?: string;
13
- }
14
-
15
- const correlationStorage = new AsyncLocalStorage<CorrelationContext>();
16
-
17
- // Standard headers for correlation
18
- export const CORRELATION_ID_HEADER = 'X-Correlation-ID';
19
- export const REQUEST_ID_HEADER = 'X-Request-ID';
20
-
21
- /**
22
- * Get the current correlation context
23
- */
24
- export function getCorrelationContext(): CorrelationContext | undefined {
25
- return correlationStorage.getStore();
26
- }
27
-
28
- /**
29
- * Run a function with a correlation context
30
- */
31
- export function withCorrelationContext<T>(ctx: CorrelationContext, fn: () => T): T {
32
- return correlationStorage.run(ctx, fn);
33
- }
34
-
35
- /**
36
- * Run an async function with a correlation context
37
- */
38
- export async function withCorrelationContextAsync<T>(ctx: CorrelationContext, fn: () => Promise<T>): Promise<T> {
39
- return correlationStorage.run(ctx, fn);
40
- }
41
-
42
- /**
43
- * Generate a new correlation ID
44
- */
45
- export function generateCorrelationId(): string {
46
- return nanoid(21);
47
- }
48
-
49
- /**
50
- * Extract correlation ID from headers (case-insensitive)
51
- */
52
- export function extractCorrelationId(headers: Record<string, string | undefined>): string {
53
- // Normalize header keys to lowercase for lookup
54
- const normalizedHeaders: Record<string, string | undefined> = {};
55
- for (const [key, value] of Object.entries(headers)) {
56
- normalizedHeaders[key.toLowerCase()] = value;
57
- }
58
-
59
- return (
60
- normalizedHeaders[CORRELATION_ID_HEADER.toLowerCase()] ||
61
- normalizedHeaders[REQUEST_ID_HEADER.toLowerCase()] ||
62
- generateCorrelationId()
63
- );
64
- }
65
-
66
- /**
67
- * Create headers for propagating correlation context to downstream services
68
- */
69
- export function createPropagationHeaders(correlationId: string): Record<string, string> {
70
- const headers: Record<string, string> = {
71
- [CORRELATION_ID_HEADER]: correlationId
72
- };
73
-
74
- // Inject OpenTelemetry trace context
75
- propagation.inject(context.active(), headers);
76
-
77
- return headers;
78
- }
79
-
80
- /**
81
- * Create correlation context from Temporal workflow memo
82
- */
83
- export function createContextFromMemo(memo: Record<string, unknown>): CorrelationContext {
84
- return {
85
- correlationId: (memo.correlationId as string) || generateCorrelationId(),
86
- merchantId: memo.merchantId as string | undefined,
87
- orderId: memo.orderId as string | undefined,
88
- workflowId: memo.workflowId as string | undefined
89
- };
90
- }
package/src/lib/logger.ts DELETED
@@ -1,77 +0,0 @@
1
- import pino from 'pino';
2
- import { trace } from '@opentelemetry/api';
3
- import { getCorrelationContext } from './context';
4
-
5
- const NODE_ENV = process.env.NODE_ENV || 'development';
6
- const SERVICE_NAME = process.env.OTEL_SERVICE_NAME || 'loop';
7
-
8
- /**
9
- * Mixin that adds trace context to every log entry
10
- */
11
- const traceMixin = () => {
12
- const mixinData: Record<string, string | undefined> = {};
13
-
14
- // Add OpenTelemetry trace context
15
- const span = trace.getActiveSpan();
16
- if (span) {
17
- const spanContext = span.spanContext();
18
- mixinData.trace_id = spanContext.traceId;
19
- mixinData.span_id = spanContext.spanId;
20
- }
21
-
22
- // Add correlation context
23
- const correlationCtx = getCorrelationContext();
24
- if (correlationCtx) {
25
- mixinData.correlation_id = correlationCtx.correlationId;
26
- if (correlationCtx.merchantId) mixinData.merchant_id = correlationCtx.merchantId;
27
- if (correlationCtx.orderId) mixinData.order_id = correlationCtx.orderId;
28
- if (correlationCtx.workflowId) mixinData.workflow_id = correlationCtx.workflowId;
29
- }
30
-
31
- return mixinData;
32
- };
33
-
34
- /**
35
- * Base logger with trace context mixin
36
- */
37
- export const logger = pino({
38
- level: NODE_ENV === 'production' ? 'info' : 'debug',
39
- mixin: traceMixin,
40
- base: {
41
- service: SERVICE_NAME,
42
- env: NODE_ENV
43
- },
44
- timestamp: pino.stdTimeFunctions.isoTime,
45
- transport: NODE_ENV !== 'production' ? { target: 'pino-pretty', options: { colorize: true } } : undefined
46
- });
47
-
48
- /**
49
- * Create a child logger for a specific activity
50
- */
51
- export function createActivityLogger(activityName: string, correlationId?: string) {
52
- return logger.child({
53
- activity: activityName,
54
- correlationId
55
- });
56
- }
57
-
58
- /**
59
- * Create a child logger for a specific workflow
60
- */
61
- export function createWorkflowLogger(workflowId: string, correlationId?: string) {
62
- return logger.child({
63
- workflowId,
64
- correlationId
65
- });
66
- }
67
-
68
- /**
69
- * Create a child logger for HTTP requests
70
- */
71
- export function createRequestLogger(requestId: string, method: string, path: string) {
72
- return logger.child({
73
- requestId,
74
- method,
75
- path
76
- });
77
- }
@@ -1,146 +0,0 @@
1
- import { metrics, ValueType } from '@opentelemetry/api';
2
-
3
- // Create a meter for the application
4
- const meter = metrics.getMeter('payloops');
5
-
6
- // =============================================================================
7
- // Payment Metrics
8
- // =============================================================================
9
-
10
- export const paymentCounter = meter.createCounter('payments_total', {
11
- description: 'Total number of payment attempts',
12
- valueType: ValueType.INT
13
- });
14
-
15
- export const paymentAmountHistogram = meter.createHistogram('payment_amount', {
16
- description: 'Distribution of payment amounts',
17
- unit: 'cents',
18
- valueType: ValueType.INT
19
- });
20
-
21
- export const paymentLatencyHistogram = meter.createHistogram('payment_latency_ms', {
22
- description: 'Payment processing latency',
23
- unit: 'ms',
24
- valueType: ValueType.DOUBLE
25
- });
26
-
27
- // =============================================================================
28
- // Webhook Metrics
29
- // =============================================================================
30
-
31
- export const webhookDeliveryCounter = meter.createCounter('webhook_deliveries_total', {
32
- description: 'Total webhook delivery attempts',
33
- valueType: ValueType.INT
34
- });
35
-
36
- export const webhookLatencyHistogram = meter.createHistogram('webhook_latency_ms', {
37
- description: 'Webhook delivery latency',
38
- unit: 'ms',
39
- valueType: ValueType.DOUBLE
40
- });
41
-
42
- // =============================================================================
43
- // HTTP Metrics
44
- // =============================================================================
45
-
46
- export const httpRequestCounter = meter.createCounter('http_requests_total', {
47
- description: 'Total HTTP requests',
48
- valueType: ValueType.INT
49
- });
50
-
51
- export const httpRequestLatencyHistogram = meter.createHistogram('http_request_latency_ms', {
52
- description: 'HTTP request latency',
53
- unit: 'ms',
54
- valueType: ValueType.DOUBLE
55
- });
56
-
57
- export const activeRequestsGauge = meter.createUpDownCounter('http_active_requests', {
58
- description: 'Number of active HTTP requests',
59
- valueType: ValueType.INT
60
- });
61
-
62
- // =============================================================================
63
- // Database Metrics
64
- // =============================================================================
65
-
66
- export const dbQueryHistogram = meter.createHistogram('db_query_duration_ms', {
67
- description: 'Database query duration',
68
- unit: 'ms',
69
- valueType: ValueType.DOUBLE
70
- });
71
-
72
- export const dbConnectionGauge = meter.createUpDownCounter('db_connections_active', {
73
- description: 'Number of active database connections',
74
- valueType: ValueType.INT
75
- });
76
-
77
- // =============================================================================
78
- // Temporal Workflow Metrics
79
- // =============================================================================
80
-
81
- export const workflowStartedCounter = meter.createCounter('workflow_started_total', {
82
- description: 'Total workflows started',
83
- valueType: ValueType.INT
84
- });
85
-
86
- export const workflowCompletedCounter = meter.createCounter('workflow_completed_total', {
87
- description: 'Total workflows completed',
88
- valueType: ValueType.INT
89
- });
90
-
91
- export const workflowFailedCounter = meter.createCounter('workflow_failed_total', {
92
- description: 'Total workflows failed',
93
- valueType: ValueType.INT
94
- });
95
-
96
- export const activityLatencyHistogram = meter.createHistogram('activity_latency_ms', {
97
- description: 'Activity execution latency',
98
- unit: 'ms',
99
- valueType: ValueType.DOUBLE
100
- });
101
-
102
- // =============================================================================
103
- // Helper Functions
104
- // =============================================================================
105
-
106
- export function recordPaymentAttempt(processor: string, currency: string, status: 'success' | 'failed' | 'pending') {
107
- paymentCounter.add(1, { processor, currency, status });
108
- }
109
-
110
- export function recordPaymentAmount(amount: number, processor: string, currency: string) {
111
- paymentAmountHistogram.record(amount, { processor, currency });
112
- }
113
-
114
- export function recordPaymentLatency(durationMs: number, processor: string, status: 'success' | 'failed') {
115
- paymentLatencyHistogram.record(durationMs, { processor, status });
116
- }
117
-
118
- export function recordWebhookDelivery(status: 'success' | 'failed', attempt: number) {
119
- webhookDeliveryCounter.add(1, { status, attempt: String(attempt) });
120
- }
121
-
122
- export function recordWebhookLatency(durationMs: number, status: 'success' | 'failed') {
123
- webhookLatencyHistogram.record(durationMs, { status });
124
- }
125
-
126
- export function recordHttpRequest(method: string, path: string, statusCode: number, durationMs: number) {
127
- const statusClass = `${Math.floor(statusCode / 100)}xx`;
128
- httpRequestCounter.add(1, { method, path, status_code: String(statusCode), status_class: statusClass });
129
- httpRequestLatencyHistogram.record(durationMs, { method, path, status_class: statusClass });
130
- }
131
-
132
- export function recordWorkflowStarted(workflowType: string, taskQueue: string) {
133
- workflowStartedCounter.add(1, { workflow_type: workflowType, task_queue: taskQueue });
134
- }
135
-
136
- export function recordWorkflowCompleted(workflowType: string, taskQueue: string, durationMs: number) {
137
- workflowCompletedCounter.add(1, { workflow_type: workflowType, task_queue: taskQueue });
138
- }
139
-
140
- export function recordWorkflowFailed(workflowType: string, taskQueue: string, errorType: string) {
141
- workflowFailedCounter.add(1, { workflow_type: workflowType, task_queue: taskQueue, error_type: errorType });
142
- }
143
-
144
- export function recordActivityLatency(activityType: string, durationMs: number, status: 'success' | 'failed') {
145
- activityLatencyHistogram.record(durationMs, { activity_type: activityType, status });
146
- }
package/src/lib/otel.ts DELETED
@@ -1,75 +0,0 @@
1
- import { NodeSDK } from '@opentelemetry/sdk-node';
2
- import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
3
- import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http';
4
- import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
5
- import { Resource } from '@opentelemetry/resources';
6
- import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from '@opentelemetry/semantic-conventions';
7
- import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
8
-
9
- let sdk: NodeSDK | null = null;
10
-
11
- export interface TelemetryConfig {
12
- serviceName: string;
13
- serviceVersion?: string;
14
- otlpEndpoint?: string;
15
- environment?: string;
16
- enabledInstrumentations?: {
17
- fs?: boolean;
18
- http?: boolean;
19
- pg?: boolean;
20
- };
21
- }
22
-
23
- export function initTelemetry(config: TelemetryConfig | string, serviceVersion = '0.0.1'): NodeSDK {
24
- if (sdk) return sdk;
25
-
26
- // Support both string (legacy) and config object
27
- const cfg: TelemetryConfig =
28
- typeof config === 'string' ? { serviceName: config, serviceVersion } : config;
29
-
30
- const otlpEndpoint = cfg.otlpEndpoint || process.env.OTEL_EXPORTER_OTLP_ENDPOINT || 'http://localhost:4318';
31
- const environment = cfg.environment || process.env.NODE_ENV || 'development';
32
-
33
- sdk = new NodeSDK({
34
- resource: new Resource({
35
- [ATTR_SERVICE_NAME]: cfg.serviceName,
36
- [ATTR_SERVICE_VERSION]: cfg.serviceVersion || '0.0.1',
37
- 'deployment.environment': environment
38
- }),
39
-
40
- traceExporter: new OTLPTraceExporter({
41
- url: `${otlpEndpoint}/v1/traces`
42
- }),
43
-
44
- metricReader: new PeriodicExportingMetricReader({
45
- exporter: new OTLPMetricExporter({
46
- url: `${otlpEndpoint}/v1/metrics`
47
- }),
48
- exportIntervalMillis: 30000
49
- }),
50
-
51
- instrumentations: [
52
- getNodeAutoInstrumentations({
53
- '@opentelemetry/instrumentation-fs': { enabled: cfg.enabledInstrumentations?.fs ?? false },
54
- '@opentelemetry/instrumentation-http': { enabled: cfg.enabledInstrumentations?.http ?? true },
55
- '@opentelemetry/instrumentation-pg': { enabled: cfg.enabledInstrumentations?.pg ?? true }
56
- })
57
- ]
58
- });
59
-
60
- sdk.start();
61
-
62
- process.on('SIGTERM', () => {
63
- sdk
64
- ?.shutdown()
65
- .then(() => console.log('Telemetry shut down'))
66
- .catch((err) => console.error('Telemetry shutdown error', err));
67
- });
68
-
69
- return sdk;
70
- }
71
-
72
- export function shutdownTelemetry(): Promise<void> {
73
- if (!sdk) return Promise.resolve();
74
- return sdk.shutdown();
75
- }
package/tsconfig.json DELETED
@@ -1,16 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "ESNext",
5
- "moduleResolution": "bundler",
6
- "lib": ["ES2022"],
7
- "strict": true,
8
- "esModuleInterop": true,
9
- "skipLibCheck": true,
10
- "declaration": true,
11
- "outDir": "dist",
12
- "rootDir": "src"
13
- },
14
- "include": ["src/**/*"],
15
- "exclude": ["node_modules", "dist"]
16
- }