@@ -11,6 +11,7 @@ import type { FilterValue } from "../../../src/lib/formatters/local.js";
1111import {
1212 formatErrorItem ,
1313 formatItem ,
14+ formatItemJson ,
1415 formatSingleLog ,
1516 formatTime ,
1617 formatTransactionItem ,
@@ -545,3 +546,153 @@ describe("inferSource", () => {
545546 expect ( result ) . toContain ( "[SERVER]" ) ;
546547 } ) ;
547548} ) ;
549+
550+ describe ( "formatItemJson" , ( ) => {
551+ const serverHeader = { sdk : { name : "sentry.node" } } ;
552+ const browserHeader = { sdk : { name : "sentry.javascript.browser" } } ;
553+
554+ test ( "formats error with exception and stack frame" , ( ) => {
555+ const event = {
556+ timestamp : 1_700_000_000 ,
557+ exception : {
558+ values : [
559+ {
560+ type : "TypeError" ,
561+ value : "x is not a function" ,
562+ stacktrace : {
563+ frames : [
564+ {
565+ filename : "src/handler.ts" ,
566+ lineno : 42 ,
567+ colno : 5 ,
568+ function : "handleRequest" ,
569+ in_app : true ,
570+ } ,
571+ ] ,
572+ } ,
573+ } ,
574+ ] ,
575+ } ,
576+ } ;
577+ const lines = formatItemJson ( "error" , event , serverHeader ) ;
578+ expect ( lines ) . toHaveLength ( 1 ) ;
579+ const parsed = JSON . parse ( lines [ 0 ] ) ;
580+ expect ( parsed . type ) . toBe ( "error" ) ;
581+ expect ( parsed . error_type ) . toBe ( "TypeError" ) ;
582+ expect ( parsed . message ) . toBe ( "x is not a function" ) ;
583+ expect ( parsed . filename ) . toBe ( "src/handler.ts" ) ;
584+ expect ( parsed . lineno ) . toBe ( 42 ) ;
585+ expect ( parsed . colno ) . toBe ( 5 ) ;
586+ expect ( parsed . function ) . toBe ( "handleRequest" ) ;
587+ expect ( parsed . source ) . toBe ( "server" ) ;
588+ } ) ;
589+
590+ test ( "formats error without stack frame" , ( ) => {
591+ const event = {
592+ timestamp : 1_700_000_000 ,
593+ message : "Something went wrong" ,
594+ } ;
595+ const lines = formatItemJson ( "error" , event , serverHeader ) ;
596+ const parsed = JSON . parse ( lines [ 0 ] ) ;
597+ expect ( parsed . error_type ) . toBe ( "Error" ) ;
598+ expect ( parsed . message ) . toBe ( "Something went wrong" ) ;
599+ expect ( parsed . filename ) . toBeUndefined ( ) ;
600+ } ) ;
601+
602+ test ( "formats event type as error" , ( ) => {
603+ const event = { timestamp : 1_700_000_000 , message : "boom" } ;
604+ const lines = formatItemJson ( "event" , event , serverHeader ) ;
605+ const parsed = JSON . parse ( lines [ 0 ] ) ;
606+ expect ( parsed . type ) . toBe ( "error" ) ;
607+ } ) ;
608+
609+ test ( "formats transaction with semantic attributes" , ( ) => {
610+ const event = {
611+ timestamp : 1_700_000_002 ,
612+ start_timestamp : 1_700_000_000 ,
613+ transaction : "process_request" ,
614+ contexts : {
615+ trace : {
616+ op : "ai.pipeline" ,
617+ data : {
618+ "gen_ai.operation.name" : "chat" ,
619+ "gen_ai.request.model" : "gpt-4o" ,
620+ } ,
621+ } ,
622+ } ,
623+ spans : [ { } , { } , { } ] ,
624+ } ;
625+ const lines = formatItemJson ( "transaction" , event , serverHeader ) ;
626+ expect ( lines ) . toHaveLength ( 1 ) ;
627+ const parsed = JSON . parse ( lines [ 0 ] ) ;
628+ expect ( parsed . type ) . toBe ( "transaction" ) ;
629+ expect ( parsed . op ) . toBe ( "gen_ai" ) ;
630+ expect ( parsed . label ) . toBe ( "chat gpt-4o" ) ;
631+ expect ( parsed . duration_ms ) . toBe ( 2000 ) ;
632+ expect ( parsed . span_count ) . toBe ( 3 ) ;
633+ expect ( parsed . source ) . toBe ( "server" ) ;
634+ } ) ;
635+
636+ test ( "formats transaction without semantic attributes" , ( ) => {
637+ const event = {
638+ timestamp : 1_700_000_001 ,
639+ start_timestamp : 1_700_000_000 ,
640+ transaction : "GET /api/users" ,
641+ contexts : { trace : { op : "http.server" } } ,
642+ } ;
643+ const lines = formatItemJson ( "transaction" , event , serverHeader ) ;
644+ const parsed = JSON . parse ( lines [ 0 ] ) ;
645+ expect ( parsed . label ) . toBe ( "GET /api/users" ) ;
646+ expect ( parsed . op ) . toBe ( "http.server" ) ;
647+ } ) ;
648+
649+ test ( "formats log entries" , ( ) => {
650+ const event = {
651+ items : [
652+ {
653+ level : "info" ,
654+ body : "User logged in" ,
655+ timestamp : 1_700_000_000 ,
656+ attributes : {
657+ "sentry.sdk.name" : { value : "node" } ,
658+ user_id : { value : 42 } ,
659+ } ,
660+ } ,
661+ { level : "debug" , body : "Cache hit" } ,
662+ ] ,
663+ } ;
664+ const lines = formatItemJson ( "log" , event , serverHeader ) ;
665+ expect ( lines ) . toHaveLength ( 2 ) ;
666+
667+ const first = JSON . parse ( lines [ 0 ] ) ;
668+ expect ( first . type ) . toBe ( "log" ) ;
669+ expect ( first . level ) . toBe ( "info" ) ;
670+ expect ( first . message ) . toBe ( "User logged in" ) ;
671+ expect ( first . attributes ) . toEqual ( { user_id : 42 } ) ;
672+ expect ( first . attributes [ "sentry.sdk.name" ] ) . toBeUndefined ( ) ;
673+
674+ const second = JSON . parse ( lines [ 1 ] ) ;
675+ expect ( second . level ) . toBe ( "debug" ) ;
676+ expect ( second . message ) . toBe ( "Cache hit" ) ;
677+ } ) ;
678+
679+ test ( "returns empty for log with no items" , ( ) => {
680+ const lines = formatItemJson ( "log" , { items : [ ] } , serverHeader ) ;
681+ expect ( lines ) . toHaveLength ( 0 ) ;
682+ } ) ;
683+
684+ test ( "formats unknown item types" , ( ) => {
685+ const event = { timestamp : 1_700_000_000 } ;
686+ const lines = formatItemJson ( "attachment" , event , serverHeader ) ;
687+ expect ( lines ) . toHaveLength ( 1 ) ;
688+ const parsed = JSON . parse ( lines [ 0 ] ) ;
689+ expect ( parsed . type ) . toBe ( "attachment" ) ;
690+ } ) ;
691+
692+ test ( "detects browser source in JSON" , ( ) => {
693+ const event = { timestamp : 1_700_000_000 , message : "error" } ;
694+ const lines = formatItemJson ( "error" , event , browserHeader ) ;
695+ const parsed = JSON . parse ( lines [ 0 ] ) ;
696+ expect ( parsed . source ) . toBe ( "browser" ) ;
697+ } ) ;
698+ } ) ;
0 commit comments