@@ -5,9 +5,10 @@ import os, { tmpdir } from "node:os";
55import { DefaultTelemetryClient } from "./client" ;
66import { createFileLogger , type Logger } from "../logging" ;
77import { LOG_LEVEL } from "../logging" ;
8- import { assertLogsMatch , TestGlobalConfigAccessor } from "../testing" ;
8+ import { assertLogsMatch , createSilentLogger , TestGlobalConfigAccessor } from "../testing" ;
99import type { MetricSink } from "./types" ;
1010import { FileSystemSink } from "./fileSystemSink" ;
11+ import { DEFAULT_GLOBAL_CONFIG } from "../globalConfig" ;
1112import { PACKAGE_VERSION } from "../constants" ;
1213
1314describe ( "DefaultTelemetryClient" , ( ) => {
@@ -28,9 +29,20 @@ describe("DefaultTelemetryClient", () => {
2829
2930 test ( "emits complete metrics to configured JSONL filesystem sinks" , async ( ) => {
3031 const auditFilePath = join ( tempDir , "telemetry" , "audit.jsonl" ) ;
32+ const sinkResourceAttributes = {
33+ "service.name" : "agentcore-cli" as const ,
34+ "service.version" : "0.0.0" ,
35+ "agentcore-cli.installation_id" : "00000000-0000-0000-0000-000000000000" ,
36+ "agentcore-cli.session_id" : "00000000-0000-0000-0000-000000000000" ,
37+ "os.type" : os . type ( ) ,
38+ "os.version" : os . release ( ) ,
39+ "host.arch" : os . arch ( ) ,
40+ "node.version" : process . version ,
41+ } ;
3142 const fileSystemSink = new FileSystemSink ( {
3243 logger : logger . child ( { module : "fileSystemSink" } ) ,
3344 filePath : auditFilePath ,
45+ resourceAttributes : sinkResourceAttributes ,
3446 } ) ;
3547 const sessionId = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" ;
3648 const globalConfigAccessor = new TestGlobalConfigAccessor ( ) ;
@@ -59,32 +71,19 @@ describe("DefaultTelemetryClient", () => {
5971
6072 expect ( fileSystemSink . getName ( ) ) . toBe ( "FileSystemSink" ) ;
6173
62- const { installationId } = await globalConfigAccessor . get ( ) ;
63-
6474 const auditContents = await readFile ( auditFilePath , "utf8" ) ;
6575
6676 const entries = auditContents
6777 . trimEnd ( )
6878 . split ( "\n" )
6979 . map ( ( line ) => JSON . parse ( line ) ) ;
7080
71- const resourceAttributes = {
72- "service.name" : "agentcore-cli" ,
73- "service.version" : PACKAGE_VERSION ,
74- "agentcore-cli.installation_id" : installationId ,
75- "agentcore-cli.session_id" : sessionId ,
76- "os.type" : os . type ( ) ,
77- "os.version" : os . release ( ) ,
78- "host.arch" : os . arch ( ) ,
79- "node.version" : process . version ,
80- } ;
81-
8281 expect ( entries ) . toEqual ( [
8382 {
8483 metricName : "cli.command_run" ,
8584 value : 123 ,
8685 attrs : {
87- ...resourceAttributes ,
86+ ...sinkResourceAttributes ,
8887 exit_reason : "success" ,
8988 command_path : "/agentcore" ,
9089 is_tui : false ,
@@ -94,7 +93,7 @@ describe("DefaultTelemetryClient", () => {
9493 metricName : "cli.command_run" ,
9594 value : 456 ,
9695 attrs : {
97- ...resourceAttributes ,
96+ ...sinkResourceAttributes ,
9897 exit_reason : "failure" ,
9998 command_path : "/agentcore" ,
10099 is_tui : false ,
@@ -191,6 +190,16 @@ describe("DefaultTelemetryClient", () => {
191190 const sink = new FileSystemSink ( {
192191 logger : logger . child ( { module : "fileSystemSink" } ) ,
193192 filePath : tempDir ,
193+ resourceAttributes : {
194+ "service.name" : "agentcore-cli" ,
195+ "service.version" : "0.0.0" ,
196+ "agentcore-cli.installation_id" : "00000000-0000-0000-0000-000000000000" ,
197+ "agentcore-cli.session_id" : "00000000-0000-0000-0000-000000000000" ,
198+ "os.type" : os . type ( ) ,
199+ "os.version" : os . release ( ) ,
200+ "host.arch" : os . arch ( ) ,
201+ "node.version" : process . version ,
202+ } ,
194203 } ) ;
195204
196205 const client = new DefaultTelemetryClient ( {
@@ -274,3 +283,109 @@ describe("DefaultTelemetryClient", () => {
274283 ] ) ;
275284 } ) ;
276285} ) ;
286+
287+ describe ( "OtelHistogramSink" , ( ) => {
288+ let testCollector : ReturnType < typeof Bun . serve > ;
289+ let receivedBodies : any [ ] ;
290+
291+ const logger = createSilentLogger ( ) ;
292+
293+ beforeEach ( async ( ) => {
294+ receivedBodies = [ ] ;
295+ testCollector = Bun . serve ( {
296+ port : 0 ,
297+ async fetch ( req ) {
298+ const body = await req . json ( ) ;
299+ receivedBodies . push ( body ) ;
300+ return new Response ( "" , { status : 200 } ) ;
301+ } ,
302+ } ) ;
303+ } ) ;
304+
305+ afterEach ( async ( ) => {
306+ testCollector . stop ( true ) ;
307+ } ) ;
308+
309+ test . each ( [
310+ { enabled : true , expectRequests : true } ,
311+ { enabled : false , expectRequests : false } ,
312+ ] ) (
313+ "telemetry.enabled=$enabled → collector receives requests=$expectRequests" ,
314+ async ( { enabled, expectRequests } ) => {
315+ const sessionId = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" ;
316+ const exitReason = "success" ;
317+ const commandPath = "/agentcore" ;
318+ const metricName = "cli.command_run" ;
319+ const scopeName = "agentcore-cli" ;
320+ const serviceName = "agentcore-cli" ;
321+ const globalConfigAccessor = new TestGlobalConfigAccessor ( {
322+ initialConfigData : {
323+ ...DEFAULT_GLOBAL_CONFIG ,
324+ telemetry : {
325+ enabled,
326+ audit : false ,
327+ endpoint : `http://localhost:${ testCollector . port } ` ,
328+ } ,
329+ } ,
330+ } ) ;
331+
332+ const client = new DefaultTelemetryClient ( {
333+ logger,
334+ sessionId,
335+ globalConfigAccessor,
336+ } ) ;
337+
338+ const event = client . createMetricEvent ( metricName , {
339+ exit_reason : exitReason ,
340+ command_path : commandPath ,
341+ } ) ;
342+ await event . emit ( 100 ) ;
343+ await client . shutdown ( ) ;
344+
345+ if ( expectRequests ) {
346+ expect ( receivedBodies . length ) . toBeGreaterThan ( 0 ) ;
347+
348+ const body = receivedBodies [ 0 ] ;
349+ expect ( body ) . toMatchObject ( {
350+ resourceMetrics : [
351+ {
352+ resource : {
353+ attributes : expect . arrayContaining ( [
354+ { key : "service.name" , value : { stringValue : serviceName } } ,
355+ {
356+ key : "agentcore-cli.session_id" ,
357+ value : { stringValue : sessionId } ,
358+ } ,
359+ { key : "os.type" , value : { stringValue : os . type ( ) } } ,
360+ { key : "host.arch" , value : { stringValue : os . arch ( ) } } ,
361+ ] ) ,
362+ } ,
363+ scopeMetrics : [
364+ {
365+ scope : { name : scopeName } ,
366+ metrics : [
367+ {
368+ name : metricName ,
369+ histogram : {
370+ dataPoints : [
371+ {
372+ attributes : expect . arrayContaining ( [
373+ { key : "exit_reason" , value : { stringValue : exitReason } } ,
374+ { key : "command_path" , value : { stringValue : commandPath } } ,
375+ ] ) ,
376+ } ,
377+ ] ,
378+ } ,
379+ } ,
380+ ] ,
381+ } ,
382+ ] ,
383+ } ,
384+ ] ,
385+ } ) ;
386+ } else {
387+ expect ( receivedBodies ) . toHaveLength ( 0 ) ;
388+ }
389+ } ,
390+ ) ;
391+ } ) ;
0 commit comments