@unchainedshop/events 5.0.0-alpha.4 → 5.0.0-alpha.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.
@@ -1,11 +1,12 @@
1
- import { type OCSFEvent, OCSF_AUTH_ACTIVITY, OCSF_ACCOUNT_ACTIVITY, OCSF_API_ACTIVITY } from './ocsf-types.ts';
1
+ import { OCSF_AUTH_ACTIVITY, OCSF_ACCOUNT_ACTIVITY, OCSF_API_ACTIVITY } from './ocsf-types.ts';
2
2
  export * from './ocsf-types.ts';
3
- export interface AuditLogConfig {
4
- directory?: string;
3
+ export interface AuditLogOptions {
4
+ log?: boolean;
5
5
  collectorUrl?: string;
6
6
  collectorHeaders?: Record<string, string>;
7
7
  batchSize?: number;
8
8
  flushIntervalMs?: number;
9
+ maxQueueSize?: number;
9
10
  }
10
11
  export interface AuthenticationInput {
11
12
  activity: (typeof OCSF_AUTH_ACTIVITY)[keyof typeof OCSF_AUTH_ACTIVITY];
@@ -41,55 +42,34 @@ export interface ApiActivityInput {
41
42
  httpMethod?: string;
42
43
  path?: string;
43
44
  responseCode?: number;
44
- }
45
- export interface AuditLogQuery {
46
- classUids?: number[];
47
- activityIds?: number[];
48
- userId?: string;
49
- success?: boolean;
50
- startTime?: Date;
51
- endTime?: Date;
52
- limit?: number;
53
- offset?: number;
54
- }
55
- export interface VerifyResult {
56
- valid: boolean;
57
- entries: number;
58
- verified: number;
59
- error?: string;
45
+ data?: Record<string, unknown>;
60
46
  }
61
47
  export declare class AuditLog {
62
- private readonly dir;
48
+ private readonly logEnabled;
49
+ private readonly auditLogger;
63
50
  private readonly collectorUrl?;
64
51
  private readonly collectorHeaders;
65
52
  private readonly batchSize;
66
53
  private readonly flushIntervalMs;
67
- private lastEvent;
68
- private writeLock;
69
- private initialized;
54
+ private readonly maxQueueSize;
55
+ private readonly serviceName;
70
56
  private pendingEvents;
57
+ private droppedEvents;
58
+ private flushing;
71
59
  private flushTimer?;
72
- constructor(config?: AuditLogConfig);
73
- private getFilePath;
74
- private init;
75
- private computeHash;
60
+ constructor(config?: AuditLogOptions);
76
61
  private createMetadata;
77
62
  private createUser;
78
63
  private createEndpoint;
79
- private writeEvent;
64
+ private dispatch;
65
+ private enqueueForCollector;
66
+ private capPendingEvents;
80
67
  private flushToCollector;
81
- private matches;
82
68
  logAuthentication(input: AuthenticationInput): Promise<string>;
83
69
  logAccountChange(input: AccountChangeInput): Promise<string>;
84
70
  logApiActivity(input: ApiActivityInput): Promise<string>;
85
- find(query?: AuditLogQuery): Promise<OCSFEvent[]>;
86
- count(query?: AuditLogQuery): Promise<number>;
87
- getFailedLogins(params: {
88
- userId?: string;
89
- remoteAddress?: string;
90
- since?: Date;
91
- }): Promise<number>;
92
- verify(): Promise<VerifyResult>;
93
71
  close(): Promise<void>;
94
72
  }
95
- export declare function createAuditLog(config?: AuditLogConfig | string): AuditLog;
73
+ export declare function createAuditLog(config?: AuditLogOptions): AuditLog;
74
+ export declare function setAuditLogInstance(instance: AuditLog): void;
75
+ export declare function getAuditLogInstance(): AuditLog | null;
@@ -1,70 +1,36 @@
1
- import { createHash } from 'node:crypto';
2
- import { mkdir, readFile, appendFile, readdir } from 'node:fs/promises';
3
- import { join } from 'node:path';
1
+ import { hostname } from 'node:os';
4
2
  import { createLogger } from '@unchainedshop/logger';
5
- import { OCSF_CLASS, OCSF_CATEGORY, OCSF_SEVERITY, OCSF_STATUS, OCSF_AUTH_ACTIVITY, OCSF_ACCOUNT_ACTIVITY, OCSF_API_ACTIVITY, } from "./ocsf-types.js";
3
+ import { OCSF_CLASS, OCSF_CATEGORY, OCSF_SEVERITY, OCSF_STATUS, OCSF_AUTH_ACTIVITY, OCSF_ACCOUNT_ACTIVITY, OCSF_API_ACTIVITY, OCSF_API_ACTIVITY_NAMES, } from "./ocsf-types.js";
4
+ import { exportLogs, resolveCollectorUrl, resolveCollectorHeaders } from "./otlp.js";
6
5
  export * from "./ocsf-types.js";
7
6
  const logger = createLogger('unchained:audit');
8
7
  const OCSF_VERSION = '1.4.0';
9
8
  const PRODUCT_NAME = 'Unchained Engine';
10
- const PRODUCT_VERSION = '4.5';
9
+ const PRODUCT_VERSION = process.env.npm_package_version || '5.0';
11
10
  const PRODUCT_VENDOR = 'Unchained';
12
- const GENESIS_HASH = '0'.repeat(64);
11
+ const ENGINE_HOSTNAME = hostname();
13
12
  export class AuditLog {
14
- dir;
13
+ logEnabled;
14
+ auditLogger;
15
15
  collectorUrl;
16
16
  collectorHeaders;
17
17
  batchSize;
18
18
  flushIntervalMs;
19
- lastEvent = null;
20
- writeLock = Promise.resolve();
21
- initialized = false;
19
+ maxQueueSize;
20
+ serviceName;
22
21
  pendingEvents = [];
22
+ droppedEvents = 0;
23
+ flushing = false;
23
24
  flushTimer;
24
25
  constructor(config = {}) {
25
- this.dir = config.directory || './audit-logs';
26
- this.collectorUrl = config.collectorUrl;
27
- this.collectorHeaders = config.collectorHeaders || {};
26
+ this.logEnabled = config.log ?? true;
27
+ this.auditLogger = createLogger('unchained:audit');
28
+ this.collectorUrl = resolveCollectorUrl(config.collectorUrl);
29
+ this.collectorHeaders = resolveCollectorHeaders(config.collectorHeaders);
28
30
  this.batchSize = config.batchSize || 10;
29
31
  this.flushIntervalMs = config.flushIntervalMs || 5000;
30
- }
31
- getFilePath() {
32
- const date = new Date().toISOString().slice(0, 10);
33
- return join(this.dir, `audit-${date}.jsonl`);
34
- }
35
- async init() {
36
- if (this.initialized)
37
- return;
38
- await mkdir(this.dir, { recursive: true });
39
- try {
40
- const files = (await readdir(this.dir)).filter((f) => f.endsWith('.jsonl')).sort();
41
- for (let i = files.length - 1; i >= 0; i--) {
42
- const content = await readFile(join(this.dir, files[i]), 'utf-8');
43
- const lines = content.trim().split('\n').filter(Boolean);
44
- if (lines.length > 0) {
45
- const parsed = JSON.parse(lines[lines.length - 1]);
46
- if (parsed.unmapped?.hash) {
47
- this.lastEvent = parsed;
48
- break;
49
- }
50
- }
51
- }
52
- }
53
- catch {
54
- }
55
- if (this.collectorUrl && !this.flushTimer) {
56
- this.flushTimer = setInterval(() => this.flushToCollector(), this.flushIntervalMs);
57
- }
58
- this.initialized = true;
59
- }
60
- computeHash(event) {
61
- const { unmapped, ...rest } = event;
62
- const toHash = {
63
- ...rest,
64
- unmapped: unmapped ? { seq: unmapped.seq, prev_hash: unmapped.prev_hash } : undefined,
65
- };
66
- const data = JSON.stringify(toHash, Object.keys(toHash).sort());
67
- return createHash('sha256').update(data, 'utf8').digest('hex');
32
+ this.maxQueueSize = config.maxQueueSize || 1000;
33
+ this.serviceName = process.env.OTEL_SERVICE_NAME || 'unchained-engine';
68
34
  }
69
35
  createMetadata(uid) {
70
36
  return {
@@ -89,82 +55,55 @@ export class AuditLog {
89
55
  return undefined;
90
56
  return { ip };
91
57
  }
92
- async writeEvent(event) {
93
- const result = this.writeLock.then(async () => {
94
- await this.init();
95
- const prevHash = this.lastEvent?.unmapped?.hash || GENESIS_HASH;
96
- const seq = (this.lastEvent?.unmapped?.seq || 0) + 1;
97
- const eventWithChain = {
98
- ...event,
99
- unmapped: {
100
- seq,
101
- prev_hash: prevHash,
102
- hash: '',
103
- },
104
- };
105
- const hash = this.computeHash(eventWithChain);
106
- eventWithChain.unmapped.hash = hash;
107
- const line = JSON.stringify(eventWithChain);
108
- await appendFile(this.getFilePath(), line + '\n', 'utf-8');
109
- this.lastEvent = eventWithChain;
110
- if (this.collectorUrl) {
111
- this.pendingEvents.push(eventWithChain);
112
- if (this.pendingEvents.length >= this.batchSize) {
113
- this.flushToCollector().catch((err) => logger.error(`Failed to flush to collector: ${err.message}`));
114
- }
58
+ async dispatch(event) {
59
+ if (this.logEnabled) {
60
+ this.auditLogger.info(event.message || 'Audit event', { ocsf: event });
61
+ }
62
+ if (this.collectorUrl) {
63
+ this.enqueueForCollector(event);
64
+ }
65
+ return event.metadata.uid;
66
+ }
67
+ enqueueForCollector(event) {
68
+ this.pendingEvents.push(event);
69
+ this.capPendingEvents();
70
+ if (!this.flushTimer) {
71
+ this.flushTimer = setInterval(() => {
72
+ this.flushToCollector().catch((err) => logger.error(`Failed to flush audit events to collector: ${err.message}`));
73
+ }, this.flushIntervalMs);
74
+ this.flushTimer.unref?.();
75
+ }
76
+ if (this.pendingEvents.length >= this.batchSize) {
77
+ this.flushToCollector().catch((err) => logger.error(`Failed to flush audit events to collector: ${err.message}`));
78
+ }
79
+ }
80
+ capPendingEvents() {
81
+ while (this.pendingEvents.length > this.maxQueueSize) {
82
+ this.pendingEvents.shift();
83
+ this.droppedEvents += 1;
84
+ if (this.droppedEvents === 1 || this.droppedEvents % 100 === 0) {
85
+ logger.warn(`Audit collector queue exceeded ${this.maxQueueSize} events — dropped ${this.droppedEvents} events so far`);
115
86
  }
116
- logger.debug(`Audit: ${event.message || 'Event'} [class=${event.class_uid}] seq=${seq}`);
117
- return eventWithChain.metadata.uid;
118
- });
119
- this.writeLock = result;
120
- return result;
87
+ }
121
88
  }
122
89
  async flushToCollector() {
123
- if (!this.collectorUrl || this.pendingEvents.length === 0)
90
+ if (!this.collectorUrl || this.flushing || this.pendingEvents.length === 0)
124
91
  return;
125
- const events = [...this.pendingEvents];
92
+ this.flushing = true;
93
+ const events = this.pendingEvents;
126
94
  this.pendingEvents = [];
127
95
  try {
128
- const response = await fetch(this.collectorUrl, {
129
- method: 'POST',
130
- headers: {
131
- 'Content-Type': 'application/json',
132
- ...this.collectorHeaders,
133
- },
134
- body: JSON.stringify({ events }),
135
- });
136
- if (!response.ok) {
137
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
138
- }
139
- logger.debug(`Flushed ${events.length} events to collector`);
96
+ await exportLogs(this.collectorUrl, this.collectorHeaders, events);
97
+ logger.debug(`Flushed ${events.length} audit events to collector`);
140
98
  }
141
99
  catch (err) {
142
100
  this.pendingEvents = [...events, ...this.pendingEvents];
101
+ this.capPendingEvents();
143
102
  throw err;
144
103
  }
145
- }
146
- matches(event, query) {
147
- if (query.classUids?.length && !query.classUids.includes(event.class_uid))
148
- return false;
149
- if (query.activityIds?.length && !query.activityIds.includes(event.activity_id))
150
- return false;
151
- const userId = 'user' in event ? event.user?.uid : undefined;
152
- const actorId = 'actor' in event ? event.actor?.user?.uid : undefined;
153
- if (query.userId && userId !== query.userId && actorId !== query.userId)
154
- return false;
155
- if (query.success !== undefined) {
156
- const isSuccess = event.status_id === OCSF_STATUS.SUCCESS;
157
- if (query.success !== isSuccess)
158
- return false;
159
- }
160
- if (query.startTime || query.endTime) {
161
- const ts = new Date(event.time);
162
- if (query.startTime && ts < query.startTime)
163
- return false;
164
- if (query.endTime && ts > query.endTime)
165
- return false;
104
+ finally {
105
+ this.flushing = false;
166
106
  }
167
- return true;
168
107
  }
169
108
  async logAuthentication(input) {
170
109
  const activityId = input.activity;
@@ -183,12 +122,13 @@ export class AuditLog {
183
122
  message: input.message || activityMessages[activityId] || 'Authentication Event',
184
123
  metadata: this.createMetadata(crypto.randomUUID()),
185
124
  user: this.createUser(input.userId, input.userName),
125
+ service: { name: this.serviceName },
186
126
  src_endpoint: this.createEndpoint(input.remoteAddress),
187
127
  is_mfa: input.isMfa,
188
128
  auth_protocol: input.authProtocol,
189
129
  session: input.sessionId ? { uid: input.sessionId } : undefined,
190
130
  };
191
- return this.writeEvent(event);
131
+ return this.dispatch(event);
192
132
  }
193
133
  async logAccountChange(input) {
194
134
  const activityId = input.activity;
@@ -220,7 +160,7 @@ export class AuditLog {
220
160
  : undefined,
221
161
  src_endpoint: this.createEndpoint(input.remoteAddress),
222
162
  };
223
- return this.writeEvent(event);
163
+ return this.dispatch(event);
224
164
  }
225
165
  async logApiActivity(input) {
226
166
  const activityId = input.activity;
@@ -238,11 +178,14 @@ export class AuditLog {
238
178
  };
239
179
  const isAccessDenied = activityId === OCSF_API_ACTIVITY.ACCESS_DENIED;
240
180
  const severity = isAccessDenied || input.success === false ? OCSF_SEVERITY.HIGH : OCSF_SEVERITY.INFORMATIONAL;
181
+ const isExtensionActivity = activityId >= 90 && activityId < 99;
182
+ const wireActivityId = isExtensionActivity ? OCSF_API_ACTIVITY.OTHER : activityId;
241
183
  const event = {
242
184
  category_uid: OCSF_CATEGORY.APPLICATION_ACTIVITY,
243
185
  class_uid: OCSF_CLASS.API_ACTIVITY,
244
- type_uid: OCSF_CLASS.API_ACTIVITY * 100 + activityId,
245
- activity_id: activityId,
186
+ type_uid: OCSF_CLASS.API_ACTIVITY * 100 + wireActivityId,
187
+ activity_id: wireActivityId,
188
+ activity_name: OCSF_API_ACTIVITY_NAMES[activityId],
246
189
  severity_id: severity,
247
190
  status_id: input.success === false ? OCSF_STATUS.FAILURE : OCSF_STATUS.SUCCESS,
248
191
  time: Date.now(),
@@ -256,139 +199,16 @@ export class AuditLog {
256
199
  operation: input.operation,
257
200
  response: input.responseCode ? { code: input.responseCode } : undefined,
258
201
  },
259
- src_endpoint: this.createEndpoint(input.remoteAddress),
202
+ src_endpoint: this.createEndpoint(input.remoteAddress) || { hostname: ENGINE_HOSTNAME },
260
203
  http_request: input.httpMethod || input.path
261
204
  ? {
262
205
  http_method: input.httpMethod,
263
206
  url: input.path ? { path: input.path } : undefined,
264
207
  }
265
208
  : undefined,
209
+ ...(input.data ? { unmapped: { data: input.data } } : {}),
266
210
  };
267
- return this.writeEvent(event);
268
- }
269
- async find(query = {}) {
270
- await this.init();
271
- const results = [];
272
- const limit = query.limit || 100;
273
- const offset = query.offset || 0;
274
- let skipped = 0;
275
- try {
276
- const files = (await readdir(this.dir)).filter((f) => f.endsWith('.jsonl')).sort();
277
- for (let i = files.length - 1; i >= 0 && results.length < limit; i--) {
278
- const content = await readFile(join(this.dir, files[i]), 'utf-8');
279
- const lines = content.trim().split('\n').filter(Boolean);
280
- for (let j = lines.length - 1; j >= 0 && results.length < limit; j--) {
281
- try {
282
- const event = JSON.parse(lines[j]);
283
- if (!this.matches(event, query))
284
- continue;
285
- if (skipped < offset) {
286
- skipped++;
287
- continue;
288
- }
289
- results.push(event);
290
- }
291
- catch {
292
- }
293
- }
294
- }
295
- }
296
- catch {
297
- }
298
- return results;
299
- }
300
- async count(query = {}) {
301
- await this.init();
302
- let count = 0;
303
- try {
304
- const files = (await readdir(this.dir)).filter((f) => f.endsWith('.jsonl')).sort();
305
- for (const file of files) {
306
- const content = await readFile(join(this.dir, file), 'utf-8');
307
- const lines = content.trim().split('\n').filter(Boolean);
308
- for (const line of lines) {
309
- try {
310
- const event = JSON.parse(line);
311
- if (this.matches(event, query))
312
- count++;
313
- }
314
- catch {
315
- }
316
- }
317
- }
318
- }
319
- catch {
320
- }
321
- return count;
322
- }
323
- async getFailedLogins(params) {
324
- const events = await this.find({
325
- classUids: [OCSF_CLASS.AUTHENTICATION],
326
- activityIds: [OCSF_AUTH_ACTIVITY.LOGON],
327
- userId: params.userId,
328
- success: false,
329
- startTime: params.since,
330
- });
331
- if (params.remoteAddress) {
332
- return events.filter((e) => {
333
- const authEvent = e;
334
- return authEvent.src_endpoint?.ip === params.remoteAddress;
335
- }).length;
336
- }
337
- return events.length;
338
- }
339
- async verify() {
340
- await this.init();
341
- let entries = 0;
342
- let verified = 0;
343
- let prevHash = GENESIS_HASH;
344
- let prevSeq = 0;
345
- try {
346
- const files = (await readdir(this.dir)).filter((f) => f.endsWith('.jsonl')).sort();
347
- for (const file of files) {
348
- const content = await readFile(join(this.dir, file), 'utf-8');
349
- const lines = content.trim().split('\n').filter(Boolean);
350
- for (const line of lines) {
351
- entries++;
352
- let event;
353
- try {
354
- event = JSON.parse(line);
355
- }
356
- catch {
357
- return { valid: false, entries, verified, error: `Parse error at entry ${entries}` };
358
- }
359
- if (!event.unmapped) {
360
- return { valid: false, entries, verified, error: `Missing hash chain at entry ${entries}` };
361
- }
362
- if (event.unmapped.seq !== prevSeq + 1) {
363
- return { valid: false, entries, verified, error: `Sequence gap at ${event.unmapped.seq}` };
364
- }
365
- if (event.unmapped.prev_hash !== prevHash) {
366
- return {
367
- valid: false,
368
- entries,
369
- verified,
370
- error: `Chain broken at seq ${event.unmapped.seq}`,
371
- };
372
- }
373
- const computedHash = this.computeHash(event);
374
- if (computedHash !== event.unmapped.hash) {
375
- return {
376
- valid: false,
377
- entries,
378
- verified,
379
- error: `Hash mismatch at seq ${event.unmapped.seq}`,
380
- };
381
- }
382
- verified++;
383
- prevHash = event.unmapped.hash;
384
- prevSeq = event.unmapped.seq;
385
- }
386
- }
387
- }
388
- catch (e) {
389
- return { valid: false, entries, verified, error: `Read error: ${e}` };
390
- }
391
- return { valid: true, entries, verified };
211
+ return this.dispatch(event);
392
212
  }
393
213
  async close() {
394
214
  if (this.flushTimer) {
@@ -400,17 +220,18 @@ export class AuditLog {
400
220
  await this.flushToCollector();
401
221
  }
402
222
  catch (err) {
403
- logger.error(`Failed to flush pending events on close: ${err.message}`);
223
+ logger.error(`Failed to flush pending audit events on close: ${err.message}`);
404
224
  }
405
225
  }
406
- await this.writeLock;
407
- this.initialized = false;
408
- this.lastEvent = null;
409
226
  }
410
227
  }
411
- export function createAuditLog(config) {
412
- if (typeof config === 'string') {
413
- return new AuditLog({ directory: config });
414
- }
228
+ export function createAuditLog(config = {}) {
415
229
  return new AuditLog(config);
416
230
  }
231
+ let _auditLogInstance = null;
232
+ export function setAuditLogInstance(instance) {
233
+ _auditLogInstance = instance;
234
+ }
235
+ export function getAuditLogInstance() {
236
+ return _auditLogInstance;
237
+ }
@@ -5,7 +5,6 @@ export declare const OCSF_CATEGORY: {
5
5
  export declare const OCSF_CLASS: {
6
6
  readonly ACCOUNT_CHANGE: 3001;
7
7
  readonly AUTHENTICATION: 3002;
8
- readonly AUTHORIZE_SESSION: 3003;
9
8
  readonly API_ACTIVITY: 6003;
10
9
  };
11
10
  export declare const OCSF_SEVERITY: {
@@ -62,6 +61,7 @@ export declare const OCSF_API_ACTIVITY: {
62
61
  readonly ACCESS_DENIED: 95;
63
62
  readonly OTHER: 99;
64
63
  };
64
+ export declare const OCSF_API_ACTIVITY_NAMES: Record<number, string>;
65
65
  export interface OCSFMetadata {
66
66
  version: string;
67
67
  product: {
@@ -99,6 +99,11 @@ export interface OCSFEndpoint {
99
99
  version?: string;
100
100
  }[];
101
101
  }
102
+ export interface OCSFService {
103
+ name?: string;
104
+ uid?: string;
105
+ version?: string;
106
+ }
102
107
  export interface OCSFApi {
103
108
  operation?: string;
104
109
  service?: {
@@ -117,22 +122,20 @@ export interface OCSFBaseEvent {
117
122
  class_uid: number;
118
123
  type_uid: number;
119
124
  activity_id: number;
125
+ activity_name?: string;
120
126
  severity_id: number;
121
127
  time: number;
122
128
  message?: string;
123
129
  metadata: OCSFMetadata;
124
130
  status_id?: number;
125
131
  status_detail?: string;
126
- unmapped?: {
127
- seq: number;
128
- prev_hash: string;
129
- hash: string;
130
- };
132
+ unmapped?: Record<string, unknown>;
131
133
  }
132
134
  export interface OCSFAuthenticationEvent extends OCSFBaseEvent {
133
135
  category_uid: 3;
134
136
  class_uid: 3002;
135
137
  user: OCSFUser;
138
+ service?: OCSFService;
136
139
  src_endpoint?: OCSFEndpoint;
137
140
  dst_endpoint?: OCSFEndpoint;
138
141
  auth_protocol_id?: number;
@@ -5,7 +5,6 @@ export const OCSF_CATEGORY = {
5
5
  export const OCSF_CLASS = {
6
6
  ACCOUNT_CHANGE: 3001,
7
7
  AUTHENTICATION: 3002,
8
- AUTHORIZE_SESSION: 3003,
9
8
  API_ACTIVITY: 6003,
10
9
  };
11
10
  export const OCSF_SEVERITY = {
@@ -62,3 +61,17 @@ export const OCSF_API_ACTIVITY = {
62
61
  ACCESS_DENIED: 95,
63
62
  OTHER: 99,
64
63
  };
64
+ export const OCSF_API_ACTIVITY_NAMES = {
65
+ [OCSF_API_ACTIVITY.UNKNOWN]: 'Unknown',
66
+ [OCSF_API_ACTIVITY.CREATE]: 'Create',
67
+ [OCSF_API_ACTIVITY.READ]: 'Read',
68
+ [OCSF_API_ACTIVITY.UPDATE]: 'Update',
69
+ [OCSF_API_ACTIVITY.DELETE]: 'Delete',
70
+ [OCSF_API_ACTIVITY.CHECKOUT]: 'Checkout',
71
+ [OCSF_API_ACTIVITY.PAYMENT]: 'Payment',
72
+ [OCSF_API_ACTIVITY.REFUND]: 'Refund',
73
+ [OCSF_API_ACTIVITY.EXPORT]: 'Export',
74
+ [OCSF_API_ACTIVITY.IMPORT]: 'Import',
75
+ [OCSF_API_ACTIVITY.ACCESS_DENIED]: 'Access Denied',
76
+ [OCSF_API_ACTIVITY.OTHER]: 'Other',
77
+ };
@@ -0,0 +1,62 @@
1
+ export type OTLPAnyValue = {
2
+ stringValue: string;
3
+ } | {
4
+ boolValue: boolean;
5
+ } | {
6
+ intValue: string;
7
+ } | {
8
+ doubleValue: number;
9
+ } | {
10
+ arrayValue: {
11
+ values: OTLPAnyValue[];
12
+ };
13
+ } | {
14
+ kvlistValue: {
15
+ values: OTLPKeyValue[];
16
+ };
17
+ };
18
+ export interface OTLPKeyValue {
19
+ key: string;
20
+ value: OTLPAnyValue;
21
+ }
22
+ export interface OTLPResource {
23
+ attributes: OTLPKeyValue[];
24
+ }
25
+ export interface OTLPInstrumentationScope {
26
+ name: string;
27
+ version?: string;
28
+ }
29
+ export declare const OTLP_SEVERITY_NUMBER: {
30
+ readonly UNSPECIFIED: 0;
31
+ readonly INFO: 9;
32
+ readonly INFO2: 10;
33
+ readonly WARN: 13;
34
+ readonly ERROR: 17;
35
+ readonly FATAL: 21;
36
+ readonly FATAL4: 24;
37
+ };
38
+ export interface OTLPLogRecord {
39
+ timeUnixNano: string;
40
+ observedTimeUnixNano: string;
41
+ severityNumber: number;
42
+ severityText?: string;
43
+ body: OTLPAnyValue;
44
+ attributes: OTLPKeyValue[];
45
+ }
46
+ export interface OTLPScopeLogs {
47
+ scope: OTLPInstrumentationScope;
48
+ logRecords: OTLPLogRecord[];
49
+ }
50
+ export interface OTLPResourceLogs {
51
+ resource: OTLPResource;
52
+ scopeLogs: OTLPScopeLogs[];
53
+ }
54
+ export interface OTLPExportLogsServiceRequest {
55
+ resourceLogs: OTLPResourceLogs[];
56
+ }
57
+ export interface OTLPExportLogsServiceResponse {
58
+ partialSuccess?: {
59
+ rejectedLogRecords?: number | string;
60
+ errorMessage?: string;
61
+ };
62
+ }
@@ -0,0 +1,9 @@
1
+ export const OTLP_SEVERITY_NUMBER = {
2
+ UNSPECIFIED: 0,
3
+ INFO: 9,
4
+ INFO2: 10,
5
+ WARN: 13,
6
+ ERROR: 17,
7
+ FATAL: 21,
8
+ FATAL4: 24,
9
+ };
@@ -0,0 +1,9 @@
1
+ import type { OCSFEvent } from './ocsf-types.ts';
2
+ import { type OTLPAnyValue, type OTLPLogRecord, type OTLPExportLogsServiceRequest } from './otlp-types.ts';
3
+ export declare function toAnyValue(value: unknown): OTLPAnyValue;
4
+ export declare function encodeOCSFLogRecord(event: OCSFEvent, observedTimeMs: number): OTLPLogRecord;
5
+ export declare function buildExportLogsRequest(events: OCSFEvent[], observedTimeMs?: number): OTLPExportLogsServiceRequest;
6
+ export declare function parseOtlpHeaders(raw?: string): Record<string, string>;
7
+ export declare function resolveCollectorUrl(explicit?: string): string | undefined;
8
+ export declare function resolveCollectorHeaders(explicit?: Record<string, string>): Record<string, string>;
9
+ export declare function exportLogs(collectorUrl: string, collectorHeaders: Record<string, string>, events: OCSFEvent[]): Promise<void>;