diff --git a/projects/distributed-tracing/src/public-api.ts b/projects/distributed-tracing/src/public-api.ts index 523fd1306..327e12e1f 100644 --- a/projects/distributed-tracing/src/public-api.ts +++ b/projects/distributed-tracing/src/public-api.ts @@ -102,6 +102,7 @@ export { WaterfallData } from './shared/dashboard/widgets/waterfall/waterfall/wa export { TraceWaterfallDataSourceModel } from './shared/dashboard/data/graphql/waterfall/trace-waterfall-data-source.model'; export { traceDetailDashboard } from './pages/trace-detail/trace-detail.dashboard'; export { TraceDetailPageComponent } from './pages/trace-detail/trace-detail.page.component'; +export { LogEvent } from './shared/dashboard/widgets/waterfall/waterfall/waterfall-chart'; // Datasources export * from './shared/dashboard/widgets/trace-detail/data/trace-detail-data-source.model'; diff --git a/projects/distributed-tracing/src/shared/components/span-detail/span-data.ts b/projects/distributed-tracing/src/shared/components/span-detail/span-data.ts index 94100c3e8..e12757ee5 100644 --- a/projects/distributed-tracing/src/shared/components/span-detail/span-data.ts +++ b/projects/distributed-tracing/src/shared/components/span-detail/span-data.ts @@ -1,4 +1,5 @@ import { Dictionary } from '@hypertrace/common'; +import { LogEvent } from '../../dashboard/widgets/waterfall/waterfall/waterfall-chart'; export interface SpanData { id: string; @@ -12,4 +13,6 @@ export interface SpanData { tags: Dictionary; requestUrl: string; exitCallsBreakup?: Dictionary; + startTime: number; + logEvents?: LogEvent[]; } diff --git a/projects/distributed-tracing/src/shared/components/span-detail/span-detail.component.test.ts b/projects/distributed-tracing/src/shared/components/span-detail/span-detail.component.test.ts index 00f1d4ed4..430205f59 100644 --- a/projects/distributed-tracing/src/shared/components/span-detail/span-detail.component.test.ts +++ b/projects/distributed-tracing/src/shared/components/span-detail/span-detail.component.test.ts @@ -28,7 +28,8 @@ describe('Span detail component', () => { responseHeaders: { header1: 'value1', header2: 'value2' }, responseBody: '[{"data": 5000}]', tags: { tag1: 'value1', tag2: 'value2' }, - requestUrl: 'test-url' + requestUrl: 'test-url', + startTime: 1604567825671 }; spectator = createHost(``, { @@ -52,7 +53,8 @@ describe('Span detail component', () => { responseHeaders: { header1: 'value1', header2: 'value2' }, responseBody: '[{"data": 5000}]', tags: { tag1: 'value1', tag2: 'value2' }, - requestUrl: 'test-url' + requestUrl: 'test-url', + startTime: 1604567825671 }; spectator = createHost(``, { @@ -77,7 +79,8 @@ describe('Span detail component', () => { responseHeaders: { header1: 'value1', header2: 'value2' }, responseBody: '[{"data": 5000}]', tags: { tag1: 'value1', tag2: 'value2' }, - requestUrl: 'test-url' + requestUrl: 'test-url', + startTime: 1604567825671 }; spectator = createHost(``, { @@ -101,7 +104,8 @@ describe('Span detail component', () => { responseHeaders: {}, responseBody: '', tags: { tag1: 'value1', tag2: 'value2' }, - requestUrl: 'test-url' + requestUrl: 'test-url', + startTime: 1604567825671 }; spectator = createHost(``, { diff --git a/projects/distributed-tracing/src/shared/dashboard/data/graphql/waterfall/trace-waterfall-data-source.model.test.ts b/projects/distributed-tracing/src/shared/dashboard/data/graphql/waterfall/trace-waterfall-data-source.model.test.ts index db942fc88..97b1a9b74 100644 --- a/projects/distributed-tracing/src/shared/dashboard/data/graphql/waterfall/trace-waterfall-data-source.model.test.ts +++ b/projects/distributed-tracing/src/shared/dashboard/data/graphql/waterfall/trace-waterfall-data-source.model.test.ts @@ -96,6 +96,17 @@ describe('Trace Waterfall data source model', () => { expect.objectContaining({ name: 'errorCount' }) + ], + logEventProperties: [ + expect.objectContaining({ + name: 'attributes' + }), + expect.objectContaining({ + name: 'timestamp' + }), + expect.objectContaining({ + name: 'summary' + }) ] } }); @@ -156,6 +167,17 @@ describe('Trace Waterfall data source model', () => { expect.objectContaining({ name: 'errorCount' }) + ], + logEventProperties: [ + expect.objectContaining({ + name: 'attributes' + }), + expect.objectContaining({ + name: 'timestamp' + }), + expect.objectContaining({ + name: 'summary' + }) ] } }); diff --git a/projects/distributed-tracing/src/shared/dashboard/data/graphql/waterfall/trace-waterfall-data-source.model.ts b/projects/distributed-tracing/src/shared/dashboard/data/graphql/waterfall/trace-waterfall-data-source.model.ts index 1f6c399c1..30fccf333 100644 --- a/projects/distributed-tracing/src/shared/dashboard/data/graphql/waterfall/trace-waterfall-data-source.model.ts +++ b/projects/distributed-tracing/src/shared/dashboard/data/graphql/waterfall/trace-waterfall-data-source.model.ts @@ -13,7 +13,7 @@ import { TRACE_GQL_REQUEST } from '../../../../graphql/request/handlers/traces/trace-graphql-query-handler.service'; import { MetadataService } from '../../../../services/metadata/metadata.service'; -import { WaterfallData } from '../../../widgets/waterfall/waterfall/waterfall-chart'; +import { LogEvent, WaterfallData } from '../../../widgets/waterfall/waterfall/waterfall-chart'; import { GraphQlDataSourceModel } from '../graphql-data-source.model'; @Model({ @@ -60,6 +60,12 @@ export class TraceWaterfallDataSourceModel extends GraphQlDataSourceModel { return combineLatest([this.getTraceData(), this.getDurationAttribute()]).pipe( map(combinedData => this.mapResponseObject(combinedData[0], combinedData[1])) @@ -73,7 +79,8 @@ export class TraceWaterfallDataSourceModel extends GraphQlDataSourceModel, - errorCount: span.errorCount as number + errorCount: span.errorCount as number, + logEvents: ((span.logEvents as Dictionary) ?? {}).results })); } } diff --git a/projects/distributed-tracing/src/shared/dashboard/widgets/waterfall/waterfall/span-name/span-name-cell-data.ts b/projects/distributed-tracing/src/shared/dashboard/widgets/waterfall/waterfall/span-name/span-name-cell-data.ts index 98d5863ca..79bc000dc 100644 --- a/projects/distributed-tracing/src/shared/dashboard/widgets/waterfall/waterfall/span-name/span-name-cell-data.ts +++ b/projects/distributed-tracing/src/shared/dashboard/widgets/waterfall/waterfall/span-name/span-name-cell-data.ts @@ -4,4 +4,5 @@ export interface SpanNameCellData { apiName?: string; color?: string; hasError?: boolean; + hasLogs?: boolean; } diff --git a/projects/distributed-tracing/src/shared/dashboard/widgets/waterfall/waterfall/span-name/span-name-table-cell-renderer.component.scss b/projects/distributed-tracing/src/shared/dashboard/widgets/waterfall/waterfall/span-name/span-name-table-cell-renderer.component.scss index 512260542..874d26418 100644 --- a/projects/distributed-tracing/src/shared/dashboard/widgets/waterfall/waterfall/span-name/span-name-table-cell-renderer.component.scss +++ b/projects/distributed-tracing/src/shared/dashboard/widgets/waterfall/waterfall/span-name/span-name-table-cell-renderer.component.scss @@ -7,7 +7,7 @@ .span-title { display: grid; - grid-template-columns: 3px min-content min-content min-content auto; + grid-template-columns: 3px min-content min-content min-content min-content auto; grid-template-rows: 1fr; column-gap: 4px; @@ -41,6 +41,10 @@ .text { @include ellipsis-overflow(); } + + .log-icon { + margin: auto 0; + } } .clickable { diff --git a/projects/distributed-tracing/src/shared/dashboard/widgets/waterfall/waterfall/span-name/span-name-table-cell-renderer.component.test.ts b/projects/distributed-tracing/src/shared/dashboard/widgets/waterfall/waterfall/span-name/span-name-table-cell-renderer.component.test.ts index 626747f60..71b39664a 100644 --- a/projects/distributed-tracing/src/shared/dashboard/widgets/waterfall/waterfall/span-name/span-name-table-cell-renderer.component.test.ts +++ b/projects/distributed-tracing/src/shared/dashboard/widgets/waterfall/waterfall/span-name/span-name-table-cell-renderer.component.test.ts @@ -61,4 +61,15 @@ describe('Span name table cell renderer component', () => { expect(spectator.query('.color-bar')).toExist(); expect(spectator.query('.error-icon')).toExist(); }); + + test('should render log icon ', () => { + const spanNameDataWithColor = { + ...spanNameData, + hasLogs: true + }; + const spectator = buildComponent({ + providers: [tableCellDataProvider(spanNameDataWithColor)] + }); + expect(spectator.query('.log-icon')).toExist(); + }); }); diff --git a/projects/distributed-tracing/src/shared/dashboard/widgets/waterfall/waterfall/span-name/span-name-table-cell-renderer.component.ts b/projects/distributed-tracing/src/shared/dashboard/widgets/waterfall/waterfall/span-name/span-name-table-cell-renderer.component.ts index d310aa23b..4b8a61189 100644 --- a/projects/distributed-tracing/src/shared/dashboard/widgets/waterfall/waterfall/span-name/span-name-table-cell-renderer.component.ts +++ b/projects/distributed-tracing/src/shared/dashboard/widgets/waterfall/waterfall/span-name/span-name-table-cell-renderer.component.ts @@ -28,6 +28,13 @@ import { WaterfallTableCellType } from './span-name-cell-type'; size="${IconSize.Medium}" color="${Color.Red5}" > + ` }) diff --git a/projects/distributed-tracing/src/shared/dashboard/widgets/waterfall/waterfall/waterfall-chart.component.test.ts b/projects/distributed-tracing/src/shared/dashboard/widgets/waterfall/waterfall/waterfall-chart.component.test.ts index a28cd97be..0dd40c198 100644 --- a/projects/distributed-tracing/src/shared/dashboard/widgets/waterfall/waterfall/waterfall-chart.component.test.ts +++ b/projects/distributed-tracing/src/shared/dashboard/widgets/waterfall/waterfall/waterfall-chart.component.test.ts @@ -31,7 +31,8 @@ describe('Waterfall Chart component', () => { responseHeaders: {}, responseBody: 'Response Body', tags: {}, - errorCount: 0 + errorCount: 0, + logEvents: [] }, { id: 'second-id', @@ -52,7 +53,8 @@ describe('Waterfall Chart component', () => { responseHeaders: {}, responseBody: '', tags: {}, - errorCount: 0 + errorCount: 0, + logEvents: [] }, { id: 'third-id', @@ -73,7 +75,8 @@ describe('Waterfall Chart component', () => { responseHeaders: {}, responseBody: '', tags: {}, - errorCount: 0 + errorCount: 0, + logEvents: [] } ]; diff --git a/projects/distributed-tracing/src/shared/dashboard/widgets/waterfall/waterfall/waterfall-chart.service.ts b/projects/distributed-tracing/src/shared/dashboard/widgets/waterfall/waterfall/waterfall-chart.service.ts index 142813a93..e98e18f20 100644 --- a/projects/distributed-tracing/src/shared/dashboard/widgets/waterfall/waterfall/waterfall-chart.service.ts +++ b/projects/distributed-tracing/src/shared/dashboard/widgets/waterfall/waterfall/waterfall-chart.service.ts @@ -42,7 +42,8 @@ export class WaterfallChartService { apiName: datum.apiName, serviceName: datum.serviceName, protocolName: datum.protocolName, - hasError: datum.errorCount > 0 + hasError: datum.errorCount > 0, + hasLogs: datum.logEvents.length > 0 }, $$iconType: this.iconLookupService.forSpanType(datum.spanType)!, getChildren: () => of([]), diff --git a/projects/distributed-tracing/src/shared/dashboard/widgets/waterfall/waterfall/waterfall-chart.ts b/projects/distributed-tracing/src/shared/dashboard/widgets/waterfall/waterfall/waterfall-chart.ts index e05b239ad..c5077dc3a 100644 --- a/projects/distributed-tracing/src/shared/dashboard/widgets/waterfall/waterfall/waterfall-chart.ts +++ b/projects/distributed-tracing/src/shared/dashboard/widgets/waterfall/waterfall/waterfall-chart.ts @@ -23,6 +23,7 @@ export interface WaterfallData { responseBody?: string; tags: Dictionary; errorCount: number; + logEvents: LogEvent[]; } export interface WaterfallDataNode extends WaterfallData, Omit { @@ -37,3 +38,9 @@ export interface WaterfallChartState { children: WaterfallDataNode[]; expanded: boolean; } + +export interface LogEvent { + attributes: Dictionary; + timestamp: string; + summary: string; +} diff --git a/projects/distributed-tracing/src/shared/graphql/request/handlers/traces/trace-graphql-query-handler.service.ts b/projects/distributed-tracing/src/shared/graphql/request/handlers/traces/trace-graphql-query-handler.service.ts index 0c3a65478..7c4c9aa4d 100644 --- a/projects/distributed-tracing/src/shared/graphql/request/handlers/traces/trace-graphql-query-handler.service.ts +++ b/projects/distributed-tracing/src/shared/graphql/request/handlers/traces/trace-graphql-query-handler.service.ts @@ -73,7 +73,23 @@ export class TraceGraphQlQueryHandlerService implements GraphQlQueryHandler { const span: Span = { - [spanIdKey]: spanRawResult.id as string + [spanIdKey]: spanRawResult.id as string, + ...(!isEmpty(spanRawResult.logEvents) && { logEvents: spanRawResult.logEvents }) }; (request.spanProperties || []).forEach(property => { @@ -175,6 +192,7 @@ export interface GraphQlTraceRequest { spanLimit: number; spanId?: string; spanProperties?: Specification[]; + logEventProperties?: Specification[]; } interface TraceServerResponse { diff --git a/projects/observability/src/shared/dashboard/data/graphql/waterfall/api-trace-waterfall-data-source.model.test.ts b/projects/observability/src/shared/dashboard/data/graphql/waterfall/api-trace-waterfall-data-source.model.test.ts index 220d76c55..425196294 100644 --- a/projects/observability/src/shared/dashboard/data/graphql/waterfall/api-trace-waterfall-data-source.model.test.ts +++ b/projects/observability/src/shared/dashboard/data/graphql/waterfall/api-trace-waterfall-data-source.model.test.ts @@ -102,6 +102,17 @@ describe('Api Trace Waterfall data source model', () => { expect.objectContaining({ name: 'errorCount' }) + ], + logEventProperties: [ + expect.objectContaining({ + name: 'attributes' + }), + expect.objectContaining({ + name: 'timestamp' + }), + expect.objectContaining({ + name: 'summary' + }) ] } }); @@ -163,6 +174,17 @@ describe('Api Trace Waterfall data source model', () => { expect.objectContaining({ name: 'errorCount' }) + ], + logEventProperties: [ + expect.objectContaining({ + name: 'attributes' + }), + expect.objectContaining({ + name: 'timestamp' + }), + expect.objectContaining({ + name: 'summary' + }) ] } }); @@ -195,7 +217,8 @@ describe('Api Trace Waterfall data source model', () => { displaySpanName: 'Span Name 1', protocolName: 'Protocol Name 1', type: SpanType.Entry, - spanTags: {} + spanTags: {}, + logEvents: [] }, { [spanIdKey]: 'second-id', @@ -207,7 +230,8 @@ describe('Api Trace Waterfall data source model', () => { displaySpanName: 'Span Name 2', protocolName: 'Protocol Name 2', type: SpanType.Exit, - spanTags: {} + spanTags: {}, + logEvents: [] } ] }) @@ -227,7 +251,8 @@ describe('Api Trace Waterfall data source model', () => { apiName: 'Span Name 1', protocolName: 'Protocol Name 1', spanType: SpanType.Entry, - tags: {} + tags: {}, + logEvents: [] }, { id: 'second-id', @@ -243,7 +268,8 @@ describe('Api Trace Waterfall data source model', () => { apiName: 'Span Name 2', protocolName: 'Protocol Name 2', spanType: SpanType.Exit, - tags: {} + tags: {}, + logEvents: [] } ] }); diff --git a/projects/observability/src/shared/dashboard/data/graphql/waterfall/api-trace-waterfall-data-source.model.ts b/projects/observability/src/shared/dashboard/data/graphql/waterfall/api-trace-waterfall-data-source.model.ts index 26789a657..698263d10 100644 --- a/projects/observability/src/shared/dashboard/data/graphql/waterfall/api-trace-waterfall-data-source.model.ts +++ b/projects/observability/src/shared/dashboard/data/graphql/waterfall/api-trace-waterfall-data-source.model.ts @@ -2,6 +2,7 @@ import { DateCoercer, Dictionary } from '@hypertrace/common'; import { AttributeMetadata, GraphQlDataSourceModel, + LogEvent, MetadataService, Span, spanIdKey, @@ -65,6 +66,10 @@ export class ApiTraceWaterfallDataSourceModel extends GraphQlDataSourceModel { return this.query({ requestType: TRACE_GQL_REQUEST, @@ -75,6 +80,9 @@ export class ApiTraceWaterfallDataSourceModel extends GraphQlDataSourceModel this.specificationBuilder.attributeSpecificationForKey(attribute) + ), + logEventProperties: this.getLogEventAttributes().map(attribute => + this.specificationBuilder.attributeSpecificationForKey(attribute) ) }); } @@ -108,7 +116,8 @@ export class ApiTraceWaterfallDataSourceModel extends GraphQlDataSourceModel, - errorCount: span.errorCount as number + errorCount: span.errorCount as number, + logEvents: ((span.logEvents as Dictionary) ?? {}).results ?? [] }; } }