securenow 4.0.12 → 5.0.1
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/CONSUMING-APPS-GUIDE.md +415 -0
- package/NPM_README.md +1328 -0
- package/README.md +42 -2
- package/console-instrumentation.js +136 -0
- package/docs/ALL-FRAMEWORKS-QUICKSTART.md +455 -0
- package/docs/ENVIRONMENT-VARIABLES.md +652 -0
- package/docs/EXPRESS-SETUP-GUIDE.md +720 -0
- package/docs/INDEX.md +206 -129
- package/docs/LOGGING-GUIDE.md +708 -0
- package/docs/LOGGING-QUICKSTART.md +239 -0
- package/docs/NEXTJS-SETUP-COMPLETE.md +795 -0
- package/examples/express-with-logging.js +137 -0
- package/examples/nextjs-with-logging-example.md +301 -0
- package/package.json +14 -3
- package/tracing.d.ts +182 -89
- package/tracing.js +61 -10
package/tracing.d.ts
CHANGED
|
@@ -1,89 +1,182 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SecureNow Tracing Module TypeScript Declarations
|
|
3
|
-
*
|
|
4
|
-
* Core tracing functionality for Node.js applications.
|
|
5
|
-
* This is typically loaded via register.js, not imported directly.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Default sensitive fields that are automatically redacted from request bodies
|
|
10
|
-
*/
|
|
11
|
-
export const DEFAULT_SENSITIVE_FIELDS: readonly string[];
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Redact sensitive fields from an object (recursively)
|
|
15
|
-
*
|
|
16
|
-
* @param obj - Object to redact (can be nested)
|
|
17
|
-
* @param sensitiveFields - Array of field names to redact (case-insensitive substring match)
|
|
18
|
-
* @returns Redacted copy of the object
|
|
19
|
-
*
|
|
20
|
-
* @example
|
|
21
|
-
* ```typescript
|
|
22
|
-
* import { redactSensitiveData } from 'securenow/tracing';
|
|
23
|
-
*
|
|
24
|
-
* const data = {
|
|
25
|
-
* email: 'user@example.com',
|
|
26
|
-
* password: 'secret123',
|
|
27
|
-
* nested: {
|
|
28
|
-
* api_key: 'sk_live_abc123'
|
|
29
|
-
* }
|
|
30
|
-
* };
|
|
31
|
-
*
|
|
32
|
-
* const redacted = redactSensitiveData(data);
|
|
33
|
-
* // Result: {
|
|
34
|
-
* // email: 'user@example.com',
|
|
35
|
-
* // password: '[REDACTED]',
|
|
36
|
-
* // nested: { api_key: '[REDACTED]' }
|
|
37
|
-
* // }
|
|
38
|
-
* ```
|
|
39
|
-
*/
|
|
40
|
-
export function redactSensitiveData<T = any>(
|
|
41
|
-
obj: T,
|
|
42
|
-
sensitiveFields?: string[]
|
|
43
|
-
): T;
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Redact sensitive data from GraphQL query strings
|
|
47
|
-
*
|
|
48
|
-
* @param query - GraphQL query string
|
|
49
|
-
* @param sensitiveFields - Array of field names to redact
|
|
50
|
-
* @returns Redacted query string
|
|
51
|
-
*
|
|
52
|
-
* @example
|
|
53
|
-
* ```typescript
|
|
54
|
-
* import { redactGraphQLQuery } from 'securenow/tracing';
|
|
55
|
-
*
|
|
56
|
-
* const query = `
|
|
57
|
-
* mutation {
|
|
58
|
-
* login(email: "user@example.com", password: "secret123") {
|
|
59
|
-
* token
|
|
60
|
-
* }
|
|
61
|
-
* }
|
|
62
|
-
* `;
|
|
63
|
-
*
|
|
64
|
-
* const redacted = redactGraphQLQuery(query);
|
|
65
|
-
* // Result: mutation { login(email: "user@example.com", password: "[REDACTED]") { token } }
|
|
66
|
-
* ```
|
|
67
|
-
*/
|
|
68
|
-
export function redactGraphQLQuery(
|
|
69
|
-
query: string,
|
|
70
|
-
sensitiveFields?: string[]
|
|
71
|
-
): string;
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
*
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
*
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
1
|
+
/**
|
|
2
|
+
* SecureNow Tracing Module TypeScript Declarations
|
|
3
|
+
*
|
|
4
|
+
* Core tracing functionality for Node.js applications.
|
|
5
|
+
* This is typically loaded via register.js, not imported directly.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Default sensitive fields that are automatically redacted from request bodies
|
|
10
|
+
*/
|
|
11
|
+
export const DEFAULT_SENSITIVE_FIELDS: readonly string[];
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Redact sensitive fields from an object (recursively)
|
|
15
|
+
*
|
|
16
|
+
* @param obj - Object to redact (can be nested)
|
|
17
|
+
* @param sensitiveFields - Array of field names to redact (case-insensitive substring match)
|
|
18
|
+
* @returns Redacted copy of the object
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* import { redactSensitiveData } from 'securenow/tracing';
|
|
23
|
+
*
|
|
24
|
+
* const data = {
|
|
25
|
+
* email: 'user@example.com',
|
|
26
|
+
* password: 'secret123',
|
|
27
|
+
* nested: {
|
|
28
|
+
* api_key: 'sk_live_abc123'
|
|
29
|
+
* }
|
|
30
|
+
* };
|
|
31
|
+
*
|
|
32
|
+
* const redacted = redactSensitiveData(data);
|
|
33
|
+
* // Result: {
|
|
34
|
+
* // email: 'user@example.com',
|
|
35
|
+
* // password: '[REDACTED]',
|
|
36
|
+
* // nested: { api_key: '[REDACTED]' }
|
|
37
|
+
* // }
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export function redactSensitiveData<T = any>(
|
|
41
|
+
obj: T,
|
|
42
|
+
sensitiveFields?: string[]
|
|
43
|
+
): T;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Redact sensitive data from GraphQL query strings
|
|
47
|
+
*
|
|
48
|
+
* @param query - GraphQL query string
|
|
49
|
+
* @param sensitiveFields - Array of field names to redact
|
|
50
|
+
* @returns Redacted query string
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```typescript
|
|
54
|
+
* import { redactGraphQLQuery } from 'securenow/tracing';
|
|
55
|
+
*
|
|
56
|
+
* const query = `
|
|
57
|
+
* mutation {
|
|
58
|
+
* login(email: "user@example.com", password: "secret123") {
|
|
59
|
+
* token
|
|
60
|
+
* }
|
|
61
|
+
* }
|
|
62
|
+
* `;
|
|
63
|
+
*
|
|
64
|
+
* const redacted = redactGraphQLQuery(query);
|
|
65
|
+
* // Result: mutation { login(email: "user@example.com", password: "[REDACTED]") { token } }
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
export function redactGraphQLQuery(
|
|
69
|
+
query: string,
|
|
70
|
+
sensitiveFields?: string[]
|
|
71
|
+
): string;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* OpenTelemetry Logger interface
|
|
75
|
+
*/
|
|
76
|
+
export interface Logger {
|
|
77
|
+
emit(logRecord: LogRecord): void;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* OpenTelemetry LogRecord interface
|
|
82
|
+
*/
|
|
83
|
+
export interface LogRecord {
|
|
84
|
+
/**
|
|
85
|
+
* Severity number (OpenTelemetry standard)
|
|
86
|
+
* 5 = DEBUG, 9 = INFO, 13 = WARN, 17 = ERROR
|
|
87
|
+
*/
|
|
88
|
+
severityNumber: number;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Human-readable severity text
|
|
92
|
+
*/
|
|
93
|
+
severityText: 'DEBUG' | 'INFO' | 'WARN' | 'ERROR' | string;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Log message body
|
|
97
|
+
*/
|
|
98
|
+
body: string;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Structured attributes for filtering/searching
|
|
102
|
+
*/
|
|
103
|
+
attributes?: Record<string, any>;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* OpenTelemetry LoggerProvider interface
|
|
108
|
+
*/
|
|
109
|
+
export interface LoggerProvider {
|
|
110
|
+
getLogger(name: string, version?: string): Logger;
|
|
111
|
+
shutdown?(): Promise<void> | void;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Get a logger instance for sending structured logs to SigNoz
|
|
116
|
+
*
|
|
117
|
+
* @param name - Logger name (e.g., 'my-service', 'auth-module')
|
|
118
|
+
* @param version - Logger version (optional, defaults to '1.0.0')
|
|
119
|
+
* @returns Logger instance or null if logging is not enabled
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```typescript
|
|
123
|
+
* import { getLogger } from 'securenow/tracing';
|
|
124
|
+
*
|
|
125
|
+
* const logger = getLogger('my-service', '1.0.0');
|
|
126
|
+
*
|
|
127
|
+
* if (logger) {
|
|
128
|
+
* logger.emit({
|
|
129
|
+
* severityNumber: 9,
|
|
130
|
+
* severityText: 'INFO',
|
|
131
|
+
* body: 'User logged in',
|
|
132
|
+
* attributes: {
|
|
133
|
+
* userId: 123,
|
|
134
|
+
* username: 'john',
|
|
135
|
+
* },
|
|
136
|
+
* });
|
|
137
|
+
* }
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
export function getLogger(name?: string, version?: string): Logger | null;
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Check if logging is enabled
|
|
144
|
+
*
|
|
145
|
+
* @returns true if SECURENOW_LOGGING_ENABLED=1, false otherwise
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
* ```typescript
|
|
149
|
+
* import { isLoggingEnabled } from 'securenow/tracing';
|
|
150
|
+
*
|
|
151
|
+
* if (isLoggingEnabled()) {
|
|
152
|
+
* console.log('Logging is enabled');
|
|
153
|
+
* }
|
|
154
|
+
* ```
|
|
155
|
+
*/
|
|
156
|
+
export function isLoggingEnabled(): boolean;
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* The OpenTelemetry LoggerProvider instance (if logging is enabled)
|
|
160
|
+
* Use getLogger() instead of accessing this directly
|
|
161
|
+
*/
|
|
162
|
+
export const loggerProvider: LoggerProvider | null;
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Environment Variables (same as register.js):
|
|
166
|
+
*
|
|
167
|
+
* Required:
|
|
168
|
+
* - SECURENOW_APPID=your-app-name
|
|
169
|
+
* - SECURENOW_INSTANCE=http://host:4318
|
|
170
|
+
*
|
|
171
|
+
* Optional:
|
|
172
|
+
* - SECURENOW_LOGGING_ENABLED=1 # Enable logging (default: 1)
|
|
173
|
+
* - SECURENOW_NO_UUID=1
|
|
174
|
+
* - SECURENOW_STRICT=1
|
|
175
|
+
* - SECURENOW_CAPTURE_BODY=1
|
|
176
|
+
* - SECURENOW_MAX_BODY_SIZE=10240
|
|
177
|
+
* - SECURENOW_SENSITIVE_FIELDS=field1,field2
|
|
178
|
+
* - SECURENOW_DISABLE_INSTRUMENTATIONS=pkg1,pkg2
|
|
179
|
+
* - OTEL_LOG_LEVEL=info|debug
|
|
180
|
+
* - SECURENOW_TEST_SPAN=1
|
|
181
|
+
* - OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=... # Override logs endpoint
|
|
182
|
+
*/
|
package/tracing.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Env:
|
|
7
7
|
* SECURENOW_APPID=logical-name # or OTEL_SERVICE_NAME=logical-name
|
|
8
8
|
* SECURENOW_NO_UUID=1 # one service.name across all workers
|
|
9
|
-
* SECURENOW_INSTANCE=http://host:4318 # OTLP/HTTP base (default
|
|
9
|
+
* SECURENOW_INSTANCE=http://host:4318 # OTLP/HTTP base (default https://freetrial.securenow.ai:4318)
|
|
10
10
|
* OTEL_EXPORTER_OTLP_ENDPOINT=... # alternative base
|
|
11
11
|
* OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=... # full traces URL
|
|
12
12
|
* OTEL_EXPORTER_OTLP_HEADERS="k=v,k2=v2"
|
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
const { diag, DiagConsoleLogger, DiagLogLevel } = require('@opentelemetry/api');
|
|
22
22
|
const { NodeSDK } = require('@opentelemetry/sdk-node');
|
|
23
23
|
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http');
|
|
24
|
+
const { OTLPLogExporter } = require('@opentelemetry/exporter-logs-otlp-http');
|
|
25
|
+
const { LoggerProvider, BatchLogRecordProcessor } = require('@opentelemetry/sdk-logs');
|
|
24
26
|
const { Resource } = require('@opentelemetry/resources');
|
|
25
27
|
const { SemanticResourceAttributes } = require('@opentelemetry/semantic-conventions');
|
|
26
28
|
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');
|
|
@@ -107,8 +109,9 @@ function redactGraphQLQuery(query, sensitiveFields = DEFAULT_SENSITIVE_FIELDS) {
|
|
|
107
109
|
})();
|
|
108
110
|
|
|
109
111
|
// -------- endpoints --------
|
|
110
|
-
const endpointBase = (env('SECURENOW_INSTANCE') || env('OTEL_EXPORTER_OTLP_ENDPOINT') || '
|
|
112
|
+
const endpointBase = (env('SECURENOW_INSTANCE') || env('OTEL_EXPORTER_OTLP_ENDPOINT') || 'https://freetrial.securenow.ai:4318').replace(/\/$/, '');
|
|
111
113
|
const tracesUrl = env('OTEL_EXPORTER_OTLP_TRACES_ENDPOINT') || `${endpointBase}/v1/traces`;
|
|
114
|
+
const logsUrl = env('OTEL_EXPORTER_OTLP_LOGS_ENDPOINT') || `${endpointBase}/v1/logs`;
|
|
112
115
|
const headers = parseHeaders(env('OTEL_EXPORTER_OTLP_HEADERS'));
|
|
113
116
|
|
|
114
117
|
// -------- naming rules --------
|
|
@@ -241,6 +244,35 @@ const httpInstrumentation = new HttpInstrumentation({
|
|
|
241
244
|
},
|
|
242
245
|
});
|
|
243
246
|
|
|
247
|
+
// -------- Logging Configuration --------
|
|
248
|
+
const loggingEnabled = String(env('SECURENOW_LOGGING_ENABLED')) !== '0' && String(env('SECURENOW_LOGGING_ENABLED')).toLowerCase() !== 'false';
|
|
249
|
+
|
|
250
|
+
// Create shared resource for both traces and logs
|
|
251
|
+
const sharedResource = new Resource({
|
|
252
|
+
[SemanticResourceAttributes.SERVICE_NAME]: serviceName,
|
|
253
|
+
[SemanticResourceAttributes.SERVICE_INSTANCE_ID]: serviceInstanceId,
|
|
254
|
+
[SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]: env('NODE_ENV') || 'production',
|
|
255
|
+
[SemanticResourceAttributes.SERVICE_VERSION]: process.env.npm_package_version || undefined,
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
// Initialize LoggerProvider if logging is enabled
|
|
259
|
+
let loggerProvider = null;
|
|
260
|
+
let globalLogger = null;
|
|
261
|
+
|
|
262
|
+
if (loggingEnabled) {
|
|
263
|
+
const logExporter = new OTLPLogExporter({
|
|
264
|
+
url: logsUrl,
|
|
265
|
+
headers
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
loggerProvider = new LoggerProvider({
|
|
269
|
+
resource: sharedResource,
|
|
270
|
+
processors: [new BatchLogRecordProcessor(logExporter)],
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
globalLogger = loggerProvider.getLogger('securenow', '1.0.0');
|
|
274
|
+
}
|
|
275
|
+
|
|
244
276
|
// -------- SDK --------
|
|
245
277
|
const traceExporter = new OTLPTraceExporter({ url: tracesUrl, headers });
|
|
246
278
|
const sdk = new NodeSDK({
|
|
@@ -252,12 +284,7 @@ const sdk = new NodeSDK({
|
|
|
252
284
|
'@opentelemetry/instrumentation-http': { enabled: false }, // We use our custom one above
|
|
253
285
|
}),
|
|
254
286
|
],
|
|
255
|
-
resource:
|
|
256
|
-
[SemanticResourceAttributes.SERVICE_NAME]: serviceName,
|
|
257
|
-
[SemanticResourceAttributes.SERVICE_INSTANCE_ID]: serviceInstanceId,
|
|
258
|
-
[SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]: env('NODE_ENV') || 'production',
|
|
259
|
-
[SemanticResourceAttributes.SERVICE_VERSION]: process.env.npm_package_version || undefined,
|
|
260
|
-
}),
|
|
287
|
+
resource: sharedResource,
|
|
261
288
|
});
|
|
262
289
|
|
|
263
290
|
// -------- start / shutdown (sync/async safe) --------
|
|
@@ -265,6 +292,11 @@ const sdk = new NodeSDK({
|
|
|
265
292
|
try {
|
|
266
293
|
await Promise.resolve(sdk.start?.());
|
|
267
294
|
console.log('[securenow] OTel SDK started → %s', tracesUrl);
|
|
295
|
+
if (loggingEnabled) {
|
|
296
|
+
console.log('[securenow] 📋 Logging: ENABLED → %s', logsUrl);
|
|
297
|
+
} else {
|
|
298
|
+
console.log('[securenow] 📋 Logging: DISABLED (set SECURENOW_LOGGING_ENABLED=1 to enable)');
|
|
299
|
+
}
|
|
268
300
|
if (captureBody) {
|
|
269
301
|
console.log('[securenow] 📝 Request body capture: ENABLED (max: %d bytes, redacting %d sensitive fields)', maxBodySize, allSensitiveFields.length);
|
|
270
302
|
}
|
|
@@ -279,9 +311,28 @@ const sdk = new NodeSDK({
|
|
|
279
311
|
})();
|
|
280
312
|
|
|
281
313
|
async function safeShutdown(sig) {
|
|
282
|
-
try {
|
|
283
|
-
|
|
314
|
+
try {
|
|
315
|
+
await Promise.resolve(sdk.shutdown?.());
|
|
316
|
+
if (loggerProvider) {
|
|
317
|
+
await Promise.resolve(loggerProvider.shutdown?.());
|
|
318
|
+
}
|
|
319
|
+
console.log(`[securenow] Tracing and logging terminated on ${sig}`);
|
|
320
|
+
}
|
|
321
|
+
catch (e) { console.error('[securenow] Shutdown error:', e); }
|
|
284
322
|
finally { process.exit(0); }
|
|
285
323
|
}
|
|
286
324
|
process.on('SIGINT', () => safeShutdown('SIGINT'));
|
|
287
325
|
process.on('SIGTERM', () => safeShutdown('SIGTERM'));
|
|
326
|
+
|
|
327
|
+
// -------- Export logger for consuming applications --------
|
|
328
|
+
module.exports = {
|
|
329
|
+
loggerProvider,
|
|
330
|
+
getLogger: (name = 'default', version = '1.0.0') => {
|
|
331
|
+
if (!loggerProvider) {
|
|
332
|
+
console.warn('[securenow] Logging is not enabled. Set SECURENOW_LOGGING_ENABLED=1 to enable logging.');
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
return loggerProvider.getLogger(name, version);
|
|
336
|
+
},
|
|
337
|
+
isLoggingEnabled: () => loggingEnabled,
|
|
338
|
+
};
|