├── src ├── assets │ └── .gitkeep ├── app │ ├── main │ │ ├── main.component.css │ │ ├── main.component.spec.ts │ │ ├── main.component.html │ │ └── main.component.ts │ ├── sticky-note │ │ ├── sticky-note.component.css │ │ ├── sticky-note.component.spec.ts │ │ ├── sticky-note.component.html │ │ └── sticky-note.component.ts │ ├── dynamic-table │ │ ├── dynamic-table.component.css │ │ ├── dynamic-table.component.html │ │ ├── dynamic-table.component.spec.ts │ │ └── dynamic-table.component.ts │ ├── sequence-diagram-dialog │ │ ├── sequence-diagram-dialog.component.css │ │ ├── sequence-diagram-dialog.component.spec.ts │ │ ├── sequence-diagram-dialog.component.html │ │ └── sequence-diagram-dialog.component.ts │ ├── history │ │ ├── history.component.css │ │ ├── history.component.spec.ts │ │ ├── history.component.html │ │ └── history.component.ts │ ├── sequence-diagram │ │ ├── sequence-diagram.component.html │ │ ├── sequence-diagram.component.css │ │ ├── sequence-diagram.component.spec.ts │ │ └── sequence-diagram.component.ts │ ├── app.component.html │ ├── model │ │ ├── tag.ts │ │ ├── tag.spec.ts │ │ ├── event-model.spec.ts │ │ └── event-model.ts │ ├── tag-table │ │ ├── tag-table.component.css │ │ ├── tag-table.component.spec.ts │ │ ├── tag-table.component.html │ │ └── tag-table.component.ts │ ├── tracing-provider │ │ ├── tracingProvider.ts │ │ ├── zipkin-provider.service.spec.ts │ │ ├── jaeger-provider.service.spec.ts │ │ ├── server-side-tracing-provider.service.spec.ts │ │ ├── jaeger-provider.service.ts │ │ ├── server-side-tracing-provider.service.ts │ │ └── zipkin-provider.service.ts │ ├── app.component.ts │ ├── app-settings │ │ ├── history-example.spec.ts │ │ ├── app-settings.service.spec.ts │ │ ├── app-settings.service.ts │ │ └── history-example.ts │ ├── menu │ │ ├── menu.component.css │ │ ├── menu.component.html │ │ └── menu.component.ts │ ├── expanded-dynamic-table │ │ ├── expanded-dynamic-table.component.css │ │ ├── expanded-dynamic-table.component.spec.ts │ │ ├── expanded-dynamic-table.component.ts │ │ └── expanded-dynamic-table.component.html │ ├── tracing-provider.service.spec.ts │ ├── app.server.module.ts │ ├── tracing-provider.service.ts │ ├── app.module.ts │ └── order-manager.service.ts ├── tsconfig.server.json ├── tsconfig.app.json ├── tsconfig.spec.json ├── tslint.json ├── browserslist ├── index.html ├── main.ts ├── environments │ ├── environment.docker.ts │ ├── environment.prod.ts │ └── environment.ts ├── test.ts ├── styles.css ├── karma.conf.js └── polyfills.ts ├── .dockerignore ├── .vscode └── settings.json ├── NOTICE ├── ReadMe └── Main2.PNG ├── e2e ├── src │ ├── app.po.ts │ └── app.e2e-spec.ts ├── tsconfig.e2e.json └── protractor.conf.js ├── .editorconfig ├── tsconfig.json ├── docker-entrypoint.sh ├── .gitignore ├── Dockerfile ├── package.json ├── tslint.json ├── CONTRIBUTING.md ├── angular.json ├── README.md └── LICENSE /src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/main/main.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | node_modules 3 | -------------------------------------------------------------------------------- /src/app/sticky-note/sticky-note.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/dynamic-table/dynamic-table.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/sequence-diagram-dialog/sequence-diagram-dialog.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "zipkin" 4 | ] 5 | } -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 SAP SE or an SAP affiliate company. All rights reserved. 2 | -------------------------------------------------------------------------------- /ReadMe/Main2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP-archive/Tracer/HEAD/ReadMe/Main2.PNG -------------------------------------------------------------------------------- /src/app/history/history.component.css: -------------------------------------------------------------------------------- 1 | .selected { 2 | background: #5c5c5c; 3 | } 4 | -------------------------------------------------------------------------------- /src/app/sequence-diagram/sequence-diagram.component.html: -------------------------------------------------------------------------------- 1 |
{{MermaidData}}
2 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/app/model/tag.ts: -------------------------------------------------------------------------------- 1 | 2 | export class Tag { 3 | public key: string; 4 | public value: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/app/sequence-diagram/sequence-diagram.component.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | table { 4 | width: 100%; 5 | } 6 | 7 | .mermaid { 8 | font-size: 0px; 9 | } 10 | 11 | -------------------------------------------------------------------------------- /src/app/model/tag.spec.ts: -------------------------------------------------------------------------------- 1 | import { Tag } from './tag'; 2 | 3 | describe('Tag', () => { 4 | it('should create an instance', () => { 5 | expect(new Tag()).toBeTruthy(); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /src/app/model/event-model.spec.ts: -------------------------------------------------------------------------------- 1 | import { EventModel } from './event-model'; 2 | 3 | describe('EventModel', () => { 4 | it('should create an instance', () => { 5 | expect(new EventModel()).toBeTruthy(); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /src/app/tag-table/tag-table.component.css: -------------------------------------------------------------------------------- 1 | td.mat-cell, td.mat-footer-cell, th.mat-header-cell 2 | { 3 | border-width: 0px !important; 4 | } 5 | 6 | tr.mat-footer-row, tr.mat-row 7 | { 8 | height: 30px !important; 9 | } 10 | -------------------------------------------------------------------------------- /src/app/tracing-provider/tracingProvider.ts: -------------------------------------------------------------------------------- 1 | import { EventModel } from '../model/event-model'; 2 | 3 | export interface TracingProvider { 4 | 5 | Get(traceId: string): Promise; 6 | GetName(): string; 7 | 8 | } 9 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html' 6 | }) 7 | export class AppComponent { 8 | title = 'app'; 9 | } 10 | -------------------------------------------------------------------------------- /src/app/app-settings/history-example.spec.ts: -------------------------------------------------------------------------------- 1 | import { HistoryExample } from './history-example'; 2 | 3 | describe('HistoryExample', () => { 4 | it('should create an instance', () => { 5 | expect(new HistoryExample()).toBeTruthy(); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /src/tsconfig.server.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs" 5 | }, 6 | "angularCompilerOptions": { 7 | "entryModule": "app/app.server.module#AppServerModule" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/app/menu/menu.component.css: -------------------------------------------------------------------------------- 1 | .button { 2 | background:none; 3 | color:inherit; 4 | border:none; 5 | font: inherit; 6 | cursor: pointer; 7 | } 8 | 9 | .displayNone { 10 | display: none; 11 | } 12 | .icon { 13 | padding: 0 14px; 14 | } 15 | 16 | -------------------------------------------------------------------------------- /src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "module": "es2015", 6 | "types": [] 7 | }, 8 | "exclude": [ 9 | "src/test.ts", 10 | "**/*.spec.ts" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getMainHeading() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | 3 | describe('App', () => { 4 | let page: AppPage; 5 | 6 | beforeEach(() => { 7 | page = new AppPage(); 8 | }); 9 | 10 | it('should display welcome message', () => { 11 | page.navigateTo(); 12 | expect(page.getMainHeading()).toEqual('Hello, world!'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "module": "commonjs", 6 | "types": [ 7 | "jasmine", 8 | "node" 9 | ] 10 | }, 11 | "files": [ 12 | "test.ts", 13 | "polyfills.ts" 14 | ], 15 | "include": [ 16 | "**/*.spec.ts", 17 | "**/*.d.ts" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /src/app/expanded-dynamic-table/expanded-dynamic-table.component.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | tr.detail-row { 4 | height: 0; 5 | } 6 | 7 | .element-row td { 8 | border-bottom-width: 0; 9 | } 10 | 11 | .element-detail { 12 | overflow: hidden; 13 | display: flex; 14 | } 15 | 16 | .element-description { 17 | padding: 16px; 18 | } 19 | tr.element-row:not(.expanded-row):hover { 20 | background: #777; 21 | } 22 | -------------------------------------------------------------------------------- /src/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tslint.json", 3 | "rules": { 4 | "directive-selector": [ 5 | true, 6 | "attribute", 7 | "app", 8 | "camelCase" 9 | ], 10 | "component-selector": [ 11 | true, 12 | "element", 13 | "app", 14 | "kebab-case" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/browserslist: -------------------------------------------------------------------------------- 1 | # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | # For IE 9-11 support, please uncomment the last line of the file and adjust as needed 5 | > 0.5% 6 | last 2 versions 7 | Firefox ESR 8 | not dead 9 | # IE 9-11 -------------------------------------------------------------------------------- /src/app/app-settings/app-settings.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { appSettings } from './app-settings.service'; 4 | 5 | describe('appSettings', () => { 6 | beforeEach(() => TestBed.configureTestingModule({})); 7 | 8 | it('should be created', () => { 9 | const service: appSettings = TestBed.get(appSettings); 10 | expect(service).toBeTruthy(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/app/tracing-provider.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { TracingProviderService } from './tracing-provider.service'; 4 | 5 | describe('SearchService', () => { 6 | beforeEach(() => TestBed.configureTestingModule({})); 7 | 8 | it('should be created', () => { 9 | const service: TracingProviderService = TestBed.get(TracingProviderService); 10 | expect(service).toBeTruthy(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/app/tracing-provider/zipkin-provider.service.spec.ts: -------------------------------------------------------------------------------- 1 | 2 | import { ZipkinProviderService } from './zipkin-provider.service'; 3 | import { TestBed } from '@angular/core/testing'; 4 | 5 | describe('ZipkinService', () => { 6 | beforeEach(() => TestBed.configureTestingModule({})); 7 | 8 | it('should be created', () => { 9 | const service: ZipkinProviderService = TestBed.get(ZipkinProviderService); 10 | expect(service).toBeTruthy(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/app/tracing-provider/jaeger-provider.service.spec.ts: -------------------------------------------------------------------------------- 1 | 2 | import { TestBed } from '@angular/core/testing'; 3 | import { JaegerProviderService } from './jaeger-provider.service'; 4 | 5 | describe('JaegerProviderService', () => { 6 | beforeEach(() => TestBed.configureTestingModule({})); 7 | 8 | it('should be created', () => { 9 | const service: JaegerProviderService = TestBed.get(JaegerProviderService); 10 | expect(service).toBeTruthy(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/app/app.server.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { ServerModule } from '@angular/platform-server'; 3 | import { ModuleMapLoaderModule } from '@nguniversal/module-map-ngfactory-loader'; 4 | import { AppComponent } from './app.component'; 5 | import { AppModule } from './app.module'; 6 | 7 | @NgModule({ 8 | imports: [AppModule, ServerModule, ModuleMapLoaderModule], 9 | bootstrap: [AppComponent] 10 | }) 11 | export class AppServerModule { } 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "moduleResolution": "node", 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "target": "es5", 12 | "typeRoots": [ 13 | "node_modules/@types" 14 | ], 15 | "lib": [ 16 | "es2017", 17 | "dom" 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Tracer 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Loading Tracer... 15 | 16 | 17 | -------------------------------------------------------------------------------- /docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | mainFileName=$(ls -d /usr/share/nginx/html/main*.js) 3 | tmpFile='tmp.txt' 4 | echo "empty" > ${tmpFile} 5 | echo "main js file: $mainFileName" 6 | 7 | echo "tracer TRACER_ENV_ " 8 | env | grep "TRACER_ENV_.*" 9 | echo "String replace placeholders to env to tempFile" 10 | envsubst "$(printf '${%s}' ${!TRACER_ENV_*})"< ${mainFileName} > ${tmpFile} 11 | echo "mv ${tmpFile} ${mainFileName}" 12 | mv ${tmpFile} ${mainFileName} 13 | exec "$@" 14 | 15 | # This file has to be in a unix format !!!! -------------------------------------------------------------------------------- /src/app/tracing-provider/server-side-tracing-provider.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { ServerSideTracingProviderService } from './server-side-tracing-provider.service'; 4 | 5 | describe('ServerSideTracingProviderService', () => { 6 | beforeEach(() => TestBed.configureTestingModule({})); 7 | 8 | it('should be created', () => { 9 | const service: ServerSideTracingProviderService = TestBed.get(ServerSideTracingProviderService); 10 | expect(service).toBeTruthy(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/app/tracing-provider/jaeger-provider.service.ts: -------------------------------------------------------------------------------- 1 | import { TracingProvider } from './tracingProvider'; 2 | import { EventModel } from '../model/event-model'; 3 | import { Injectable } from '@angular/core'; 4 | 5 | @Injectable({ 6 | providedIn: 'root' 7 | }) 8 | 9 | export class JaegerProviderService implements TracingProvider { 10 | 11 | GetName(): string { 12 | return 'jaeger'; 13 | } 14 | Get(traceId: string): Promise { 15 | throw new Error("Method not implemented."); 16 | } 17 | 18 | constructor() { } 19 | } 20 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | import 'hammerjs'; 4 | 5 | import { AppModule } from './app/app.module'; 6 | import { environment } from './environments/environment'; 7 | 8 | export function getBaseUrl() { 9 | return document.getElementsByTagName('base')[0].href; 10 | } 11 | 12 | const providers = [ 13 | { provide: 'BASE_URL', useFactory: getBaseUrl, deps: [] } 14 | ]; 15 | 16 | if (environment.production) { 17 | enableProdMode(); 18 | } 19 | 20 | platformBrowserDynamic(providers).bootstrapModule(AppModule) 21 | .catch(err => console.log(err)); 22 | -------------------------------------------------------------------------------- /src/environments/environment.docker.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | docker: true, 4 | tracingProvider: 5 | { 6 | name: '${TRACER_ENV_TracingProviderName}', 7 | url: '${TRACER_ENV_TracingProviderUrl}', 8 | }, 9 | defaultStickyTags: ['tracer.traceId', 'user.*'] 10 | , 'links': [ 11 | { 12 | 'name': 'ThirdParty2', 13 | 'link': 'http://ThirdParty?StartTime={startDate}&to={endDate}&trace={traceId}' 14 | }, 15 | { 16 | 'name': 'ThirdParty1', 17 | // tslint:disable-next-line: max-line-length 18 | 'link': 'http://ThirdParty?StartTime={startDate}&to={endDate}&trace={traceId}' 19 | } 20 | ] 21 | }; 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /dist-server 6 | /tmp 7 | /out-tsc 8 | 9 | # dependencies 10 | /node_modules 11 | 12 | # IDEs and editors 13 | /.idea 14 | .project 15 | .classpath 16 | .c9/ 17 | *.launch 18 | .settings/ 19 | *.sublime-workspace 20 | 21 | # IDE - VSCode 22 | .vscode/* 23 | .vs/* 24 | !.vscode/settings.json 25 | !.vscode/tasks.json 26 | !.vscode/launch.json 27 | !.vscode/extensions.json 28 | 29 | # misc 30 | /.sass-cache 31 | /connect.lock 32 | /coverage 33 | /libpeerconnection.log 34 | npm-debug.log 35 | yarn-error.log 36 | testem.log 37 | /typings 38 | 39 | # System Files 40 | .DS_Store 41 | Thumbs.db 42 | -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | docker: false, 4 | tracingProvider: 5 | { 6 | name: 'not define', // serverSide, zipkin 7 | url: 'http://testtracer.azurewebsites.net/zipkin' //http://localhost:9411' 8 | }, 9 | defaultStickyTags: ['tracer.traceId', 'user.*'] 10 | , 'links': [ 11 | { 12 | 'name': 'ThirdParty2', 13 | 'link': 'http://ThirdParty?StartTime={startDate}&to={endDate}&trace={traceId}' 14 | }, 15 | { 16 | 'name': 'ThirdParty1', 17 | // tslint:disable-next-line: max-line-length 18 | 'link': 'http://ThirdParty?StartTime={startDate}&to={endDate}&trace={traceId}' 19 | } 20 | ] 21 | }; 22 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone-testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: any; 11 | 12 | // First, initialize the Angular testing environment. 13 | getTestBed().initTestEnvironment( 14 | BrowserDynamicTestingModule, 15 | platformBrowserDynamicTesting() 16 | ); 17 | // Then we find all the tests. 18 | const context = require.context('./', true, /\.spec\.ts$/); 19 | // And load the modules. 20 | context.keys().map(context); 21 | -------------------------------------------------------------------------------- /src/app/main/main.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { MainComponent } from './main.component'; 4 | 5 | describe('MainComponent', () => { 6 | let component: MainComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ MainComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(MainComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/history/history.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { HistoryComponent } from './history.component'; 4 | 5 | describe('HistoryComponent', () => { 6 | let component: HistoryComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ HistoryComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(HistoryComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/dynamic-table/dynamic-table.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Key 8 | {{element.key}} 9 | 10 | 11 | Value 12 | {{element.value}} 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/app/tag-table/tag-table.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { TagTableComponent } from './tag-table.component'; 4 | 5 | describe('TagTableComponent', () => { 6 | let component: TagTableComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ TagTableComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(TagTableComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/sticky-note/sticky-note.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { StickyNoteComponent } from './sticky-note.component'; 4 | 5 | describe('StickyNoteComponent', () => { 6 | let component: StickyNoteComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ StickyNoteComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(StickyNoteComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/dynamic-table/dynamic-table.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { DynamicTableComponent } from './dynamic-table.component'; 4 | 5 | describe('DynamicTableComponent', () => { 6 | let component: DynamicTableComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ DynamicTableComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(DynamicTableComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | @import "@angular/material/prebuilt-themes/purple-green.css"; 3 | 4 | .messageText { 5 | font-size: 16px; 6 | cursor: pointer; 7 | } 8 | 9 | 10 | table { 11 | width: 100%; 12 | } 13 | .mat-cell { 14 | font-size: 9pt; 15 | } 16 | 17 | .mat-footer-row, 18 | .mat-row { 19 | height: 35 !important; 20 | } 21 | .spacer { 22 | flex: 1 1 auto; 23 | } 24 | 25 | 26 | /* for firefox */ 27 | .actor { 28 | font-size: 14px ; 29 | } 30 | .activation0 ,.activation1,.activation2,.activation3,.activation4,.activation5,.activation6,.activation7,.activation8,.activation9,.activation10 31 | { 32 | fill:#303030 !important; 33 | stroke:#f8f9fa !important; 34 | } 35 | 36 | .fullHeight 37 | { 38 | height: 100%; 39 | } 40 | 41 | -------------------------------------------------------------------------------- /src/app/sequence-diagram/sequence-diagram.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { SequenceDiagramComponent } from './sequence-diagram.component'; 4 | 5 | describe('SequenceDiagramComponent', () => { 6 | let component: SequenceDiagramComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ SequenceDiagramComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(SequenceDiagramComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | const { SpecReporter } = require('jasmine-spec-reporter'); 5 | 6 | exports.config = { 7 | allScriptsTimeout: 11000, 8 | specs: [ 9 | './src/**/*.e2e-spec.ts' 10 | ], 11 | capabilities: { 12 | 'browserName': 'chrome' 13 | }, 14 | directConnect: true, 15 | baseUrl: 'http://localhost:4200/', 16 | framework: 'jasmine', 17 | jasmineNodeOpts: { 18 | showColors: true, 19 | defaultTimeoutInterval: 30000, 20 | print: function() {} 21 | }, 22 | onPrepare() { 23 | require('ts-node').register({ 24 | project: require('path').join(__dirname, './tsconfig.e2e.json') 25 | }); 26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /src/app/expanded-dynamic-table/expanded-dynamic-table.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ExpandedDynamicTableComponent } from './expanded-dynamic-table.component'; 4 | 5 | describe('ExpandedDynamicTableComponent', () => { 6 | let component: ExpandedDynamicTableComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ ExpandedDynamicTableComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ExpandedDynamicTableComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/sequence-diagram-dialog/sequence-diagram-dialog.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { SequenceDiagramDialogComponent } from './sequence-diagram-dialog.component'; 4 | 5 | describe('SequenceDiagramDialogComponent', () => { 6 | let component: SequenceDiagramDialogComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ SequenceDiagramDialogComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(SequenceDiagramDialogComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/dynamic-table/dynamic-table.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | import { Tag } from '../model/tag'; 3 | 4 | @Component({ 5 | selector: 'app-dynamic-table', 6 | templateUrl: './dynamic-table.component.html', 7 | styleUrls: ['./dynamic-table.component.css'] 8 | }) 9 | export class DynamicTableComponent { 10 | private _dataSource; 11 | public tags: Tag[]; 12 | @Input() 13 | set dataSource(dataSource: any[]) { 14 | this._dataSource = dataSource; 15 | // Filter 16 | 17 | } 18 | @Input() 19 | set columns(columns: any[]) { 20 | // Filter 21 | const tags: Tag[] = []; 22 | for (let i = 0; i < columns.length; i++) { 23 | const value = this._dataSource[columns[i].header]; 24 | if (value) { 25 | tags.push({ key: columns[i].header, value: value } as Tag); 26 | } 27 | } 28 | this.tags = tags; 29 | 30 | } 31 | 32 | displayedColumns = ['key', 'value']; 33 | constructor() { } 34 | 35 | 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/app/sequence-diagram-dialog/sequence-diagram-dialog.component.html: -------------------------------------------------------------------------------- 1 |
{{Notice}} 2 |
3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | Key 14 | {{element.key}} 15 | 16 | 17 | Value 18 | {{element.value}} 19 | 20 | 21 | 22 | 23 |
24 | -------------------------------------------------------------------------------- /src/app/tracing-provider/server-side-tracing-provider.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { TracingProvider } from './tracingProvider'; 3 | import { environment } from 'src/environments/environment'; 4 | import { EventModel } from '../model/event-model'; 5 | import { HttpClient, HttpErrorResponse } from '@angular/common/http'; 6 | 7 | @Injectable({ 8 | providedIn: 'root' 9 | }) 10 | export class ServerSideTracingProviderService implements TracingProvider { 11 | constructor(private httpClient: HttpClient) { } 12 | 13 | async Get(traceId: string): Promise { 14 | const url = `${environment.tracingProvider.url}/trace/${traceId}`; 15 | try { 16 | return await this.httpClient.get(url).toPromise(); 17 | 18 | } catch (error) { 19 | const ex = error as HttpErrorResponse; 20 | if (ex.status === 404) { 21 | return null; 22 | } 23 | throw error; 24 | } 25 | } 26 | 27 | GetName(): string { 28 | return 'serverSide'; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, '../coverage'), 20 | reports: ['html', 'lcovonly'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /src/app/history/history.component.html: -------------------------------------------------------------------------------- 1 | 2 | 4 |
{{text_truncate( 5 | item.name && item.name !== ""?item.name : item.traceId,23) 6 | }}
7 | 8 | 9 |

10 | {{item.startedAt}} 11 |

12 |

13 | error: {{text_truncate(item.error,30)}} 14 |

15 |

16 | action: {{text_truncate(item.action,25)}} 17 |

18 | 19 | 22 | 25 | 26 |
27 |
28 | -------------------------------------------------------------------------------- /src/app/sticky-note/sticky-note.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 7 | 8 | {{tag.key}}: {{tag.sortValue}} 9 | cancel 11 | 12 | 15 | 16 | 17 | {{name}} 18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build ---prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false, 7 | docker: false, 8 | tracingProvider: 9 | { 10 | name: 'not define', // serverSide, zipkin 11 | url: 'http://testtracer.azurewebsites.net/zipkin' //http://localhost:9411' 12 | }, 13 | defaultStickyTags: ['tracer.traceId', 'user.*' ] 14 | , 'links': [ 15 | { 16 | 'name': 'ThirdParty2', 17 | 'link': 'http://ThirdParty?StartTime={startDate}&to={endDate}&traceId={traceId}' 18 | }, 19 | { 20 | 'name': 'ThirdParty1', 21 | // tslint:disable-next-line: max-line-length 22 | 'link': 'http://ThirdParty?StartTime={startDate}&to={endDate}&traceId={traceId}' 23 | } 24 | ] 25 | }; 26 | 27 | /* 28 | * In development mode, to ignore zone related error stack frames such as 29 | * `zone.run`, `zoneDelegate.invokeTask` for easier debugging, you can 30 | * import the following file, but please comment it out in production mode 31 | * because it will have performance impact when throw error 32 | */ 33 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 34 | -------------------------------------------------------------------------------- /src/app/model/event-model.ts: -------------------------------------------------------------------------------- 1 | export class EventModel { 2 | tracer: Tracer = { from: {}, to: {} } as Tracer; 3 | } 4 | export interface Tracer { 5 | from: Server; 6 | to: Server; 7 | action: string; 8 | spanId: string; 9 | parentSpanId: string; 10 | traceId: string; 11 | error: string; 12 | priority: string; 13 | timestamp: number; // microseconds 14 | durationMs: number; 15 | direction: Direction; 16 | metadata: Metadata; 17 | } 18 | 19 | export interface Server { 20 | // The server can have multiple nicknames, we have to find the best name to display 21 | nickName: string; 22 | host: string; 23 | name: string; 24 | version: string; 25 | } 26 | 27 | export interface Metadata { 28 | generateParentSpanId: boolean; 29 | isFake: boolean; 30 | count: number; 31 | clientEndBeforeServer: number; 32 | serverStartAfterClient: number; 33 | visit: boolean; 34 | } 35 | 36 | 37 | 38 | 39 | /* 40 | Logical transaction: 41 | All the inner interactions will be in the same operation block . 42 | comprise of start and end, when one of them is missing it will auto generate it (The line courser will be with cross ⥇ ). 43 | Case 0 logical transaction start (striate line → ) 44 | Case 1 logical transaction end (dashed line ⇠) 45 | */ 46 | 47 | export enum Direction { 48 | LogicalTransactionStart, LogicalTransactionEnd, ActionStart 49 | } 50 | // TODO: add validation 51 | -------------------------------------------------------------------------------- /src/app/expanded-dynamic-table/expanded-dynamic-table.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, ViewChild } from '@angular/core'; 2 | import { trigger, state, style, transition, animate } from '@angular/animations'; 3 | import { MatTableDataSource } from '@angular/material/table'; 4 | import { MatSort, MatSortable } from '@angular/material/sort'; 5 | import { promise } from 'protractor'; 6 | 7 | @Component({ 8 | selector: 'app-expanded-dynamic-table', 9 | templateUrl: './expanded-dynamic-table.component.html', 10 | styleUrls: ['./expanded-dynamic-table.component.css'], 11 | animations: [ 12 | trigger('detailExpand', [ 13 | state('collapsed', style({ height: '0px', minHeight: '0' })), 14 | state('expanded', style({ height: '*' })), 15 | transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')), 16 | ]), 17 | ] 18 | }) 19 | 20 | 21 | export class ExpandedDynamicTableComponent { 22 | 23 | @ViewChild(MatSort, { static: true }) sort: MatSort; 24 | 25 | @Input() 26 | public set dataSource(dataSource: string[][]) { 27 | this._dataSource.data = dataSource || []; 28 | this._dataSource.sort = this.sort; 29 | } 30 | 31 | @Input() 32 | public set columnsToDisplay(columnsToDisplay: string[]) { 33 | this._columnsToDisplay = columnsToDisplay || []; 34 | 35 | } 36 | @Input() 37 | columns: any[]; 38 | _dataSource: MatTableDataSource = new MatTableDataSource(); 39 | _columnsToDisplay = []; 40 | expandedElement: any | null; 41 | } 42 | -------------------------------------------------------------------------------- /src/app/expanded-dynamic-table/expanded-dynamic-table.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 17 | 18 | 19 | 20 | 22 | 23 | 24 |
{{ column.header }}{{ row[column.header] }} 11 |
12 |
13 | 14 |
15 |
16 |
25 | 26 | 27 | 30 | -------------------------------------------------------------------------------- /src/app/menu/menu.component.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 10 | 14 | 18 | 19 | 20 |

Tracer

21 | 22 | 23 | 24 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 39 | 44 |
45 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ################ Build ################ 2 | FROM node:12.6-alpine AS builder 3 | 4 | WORKDIR /app 5 | 6 | # Copy project files to the docker image (except .dockerignore) 7 | COPY . . 8 | 9 | # Install Angular cli 10 | RUN npm install @angular/cli@8.1.2 -g 11 | 12 | # Install packages 13 | RUN npm install 14 | 15 | # Build Angular 16 | RUN ng build --configuration=docker 17 | ################ Build ################ 18 | 19 | 20 | ################ Serve ################ 21 | 22 | FROM nginx:alpine 23 | 24 | ############### Set Defualt Value ################ 25 | ENV TRACER_ENV_TracingProviderName=NotExists 26 | ENV TRACER_ENV_TracingProviderUrl=NotExists 27 | ############### Set Defualt Value ################ 28 | 29 | # Dist artifacts to default nginx public folder 30 | COPY --from=builder /app/dist /usr/share/nginx/html 31 | 32 | 33 | 34 | 35 | ############### EntryPoint ############### 36 | # This entrypoint is setting the env into the main.js before the nginx start on docker run 37 | 38 | # Install bash 39 | RUN apk add --update bash 40 | 41 | # COPY the entrypoint 42 | COPY --from=builder /app/docker-entrypoint.sh /entrypoint.sh 43 | 44 | #Add permissin to entrypoint 45 | RUN chmod +x ./entrypoint.sh 46 | 47 | # Set entrypoint format to linux 48 | RUN dos2unix entrypoint.sh 49 | 50 | # set entry point--> ENTRYPOINT ./entrypoint.sh has less clear errors 51 | ENTRYPOINT ["bin/bash", "./entrypoint.sh"] 52 | 53 | ############### entry point ############### 54 | 55 | # Start nginx 56 | CMD ["nginx", "-g", "daemon off;"] 57 | 58 | EXPOSE 80 59 | 60 | ################ Serve ################ 61 | 62 | 63 | #### Debug Container Output 64 | # docker run -it --rm tracer "/bin/ash" 65 | ### Extract ENV Form Container Shell 66 | # cat "$( ls /usr/share/nginx/html/main.*js)"| grep "tracingProvider[ \t]*:[^}]*" -o 67 | 68 | 69 | -------------------------------------------------------------------------------- /src/app/tracing-provider.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Inject } from '@angular/core'; 2 | import { EventModel } from './model/event-model'; 3 | import { environment } from 'src/environments/environment'; 4 | import { TracingProvider } from './tracing-provider/tracingProvider'; 5 | 6 | @Injectable({ 7 | providedIn: 'root' 8 | }) 9 | 10 | export class TracingProviderService { 11 | 12 | private TracingProvider: TracingProvider; 13 | private ErrorMessage: string; 14 | 15 | constructor(@Inject('TracingProviderService') providers: TracingProvider[]) { 16 | 17 | this.TracingProvider = providers.find(x => x.GetName() === environment.tracingProvider.name); 18 | this.ErrorMessage = 'Configuration required.' 19 | + `\n To enable search, please configure tracing provider (available providers: ${providers.map(x => x.GetName()).join(',')})` 20 | + '\n For more details: https://github.com/sap/Tracer#tracing-provider.'; 21 | 22 | if (environment.docker) { 23 | this.ErrorMessage = 'Configuration required.' 24 | + `\n To enable search, please configure tracing provider (available providers: ${providers.map(x => x.GetName()).join(',')})` 25 | + '\n You can define tracing provider by providing env TRACER_ENV_TracingProviderName ,TRACER_ENV_TracingProviderUrl.' 26 | + '\n For more details: https://github.com/sap/Tracer#tracing-provider.'; 27 | } 28 | } 29 | 30 | public HasTracingProvider() { 31 | if (this.TracingProvider) { 32 | return true; 33 | } 34 | 35 | return false; 36 | } 37 | 38 | public GetErrorMessage() { 39 | return this.ErrorMessage; 40 | } 41 | 42 | public async GetFlow(traceId: string): Promise { 43 | 44 | if (this.HasTracingProvider()) { 45 | return await this.TracingProvider.Get(traceId); 46 | } else { 47 | throw new Error(this.ErrorMessage); 48 | } 49 | 50 | } 51 | } 52 | 53 | -------------------------------------------------------------------------------- /src/app/tag-table/tag-table.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
15 | 18 | 19 | {{element.name}}
33 |
34 | 35 |
36 | 37 | 38 |
39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build", 8 | "build:ssr": "ng run Web:server:dev", 9 | "test": "ng test", 10 | "lint": "ng lint", 11 | "e2e": "ng e2e" 12 | }, 13 | "private": true, 14 | "dependencies": { 15 | "@angular/animations": "^8.2.14", 16 | "@angular/cdk": "^8.2.3", 17 | "@angular/common": "^8.2.14", 18 | "@angular/compiler": "^8.2.14", 19 | "@angular/core": "^8.2.14", 20 | "@angular/forms": "^8.2.14", 21 | "@angular/material": "^8.2.3", 22 | "@angular/platform-browser": "^8.2.14", 23 | "@angular/platform-browser-dynamic": "^8.2.14", 24 | "@angular/platform-server": "^8.2.14", 25 | "@angular/router": "^8.2.14", 26 | "@nguniversal/module-map-ngfactory-loader": "^8.2.6", 27 | "bootstrap": "^4.4.1", 28 | "core-js": "^3.6.4", 29 | "file-saver": "^2.0.2", 30 | "hammerjs": "^2.0.8", 31 | "jquery": "^3.4.1", 32 | "mermaid": "^8.2.5", 33 | "rxjs": "^6.5.4", 34 | "zone.js": "^0.10.2" 35 | }, 36 | "devDependencies": { 37 | "@angular-devkit/build-angular": "^0.803.23", 38 | "@angular/cli": "^8.3.23", 39 | "@angular/compiler-cli": "^8.2.14", 40 | "@angular/language-service": "^8.2.14", 41 | "@types/jasmine": "^3.5.1", 42 | "@types/jasminewd2": "^2.0.8", 43 | "@types/node": "^12.12.25", 44 | "codelyzer": "^5.2.1", 45 | "jasmine-core": "^3.5.0", 46 | "jasmine-spec-reporter": "~4.2.1", 47 | "karma": "^4.4.1", 48 | "karma-chrome-launcher": "~3.1.0", 49 | "karma-coverage-istanbul-reporter": "^2.1.1", 50 | "karma-jasmine": "^2.0.1", 51 | "karma-jasmine-html-reporter": "^1.5.1", 52 | "typescript": "~3.5.3" 53 | }, 54 | "optionalDependencies": { 55 | "node-sass": "^4.12.0", 56 | "protractor": "~5.4.0", 57 | "ts-node": "~5.0.1", 58 | "tslint": "~5.9.1" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/app/sequence-diagram-dialog/sequence-diagram-dialog.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input, Inject, ViewChild } from '@angular/core'; 2 | import { EventModel } from '../model/event-model'; 3 | import { Tag } from '../model/tag'; 4 | import { MAT_DIALOG_DATA } from '@angular/material/dialog'; 5 | import { MatTableDataSource } from '@angular/material/table'; 6 | import { MatSort, MatSortable } from '@angular/material/sort'; 7 | @Component({ 8 | selector: 'app-sequence-diagram-dialog', 9 | templateUrl: './sequence-diagram-dialog.component.html', 10 | styleUrls: ['./sequence-diagram-dialog.component.css'] 11 | }) 12 | export class SequenceDiagramDialogComponent implements OnInit { 13 | displayedColumnsTags: string[] = ['key', 'value']; 14 | requestTags: MatTableDataSource; 15 | 16 | @ViewChild(MatSort, { static: true }) sort: MatSort; 17 | Notice: string; 18 | 19 | constructor(@Inject(MAT_DIALOG_DATA) eventModel: EventModel) { 20 | this.Notice = ''; 21 | if (eventModel && eventModel.tracer.metadata && eventModel.tracer.metadata.isFake) { 22 | this.Notice = 'Note: This event is auto generate for the graph'; 23 | } 24 | const flatObject = this.flattenObject(eventModel); 25 | const tags: Tag[] = Object.keys(flatObject).map(key => { 26 | return { key: key, value: flatObject[key] }; 27 | }).filter(x => x.value); 28 | this.requestTags = new MatTableDataSource(tags); 29 | 30 | 31 | } 32 | ngOnInit() { 33 | this.requestTags.sort = this.sort; 34 | this.sort.sort(({ id: 'key', start: 'asc' }) as MatSortable); 35 | } 36 | applyFilter(filterValue: string) { 37 | this.requestTags.filter = filterValue.trim().toLowerCase(); 38 | } 39 | 40 | // tslint:disable-next-line: member-ordering 41 | 42 | flattenObject(ob) { 43 | const toReturn = {}; 44 | 45 | for (const i in ob) { 46 | if (!ob.hasOwnProperty(i)) { continue; } 47 | 48 | // tslint:disable-next-line: triple-equals 49 | if ((typeof ob[i]) == 'object' && ob[i] !== null) { 50 | const flatObject = this.flattenObject(ob[i]); 51 | for (const x in flatObject) { 52 | if (!flatObject.hasOwnProperty(x)) { continue; } 53 | 54 | toReturn[i + '.' + x] = flatObject[x]; 55 | } 56 | } else { 57 | toReturn[i] = ob[i]; 58 | } 59 | } 60 | return toReturn; 61 | } 62 | 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/ 22 | // import 'core-js/es6/symbol'; 23 | // import 'core-js/es6/object'; 24 | // import 'core-js/es6/function'; 25 | // import 'core-js/es6/parse-int'; 26 | // import 'core-js/es6/parse-float'; 27 | // import 'core-js/es6/number'; 28 | // import 'core-js/es6/math'; 29 | // import 'core-js/es6/string'; 30 | // import 'core-js/es6/date'; 31 | // import 'core-js/es6/array'; 32 | // import 'core-js/es6/regexp'; 33 | // import 'core-js/es6/map'; 34 | // import 'core-js/es6/weak-map'; 35 | // import 'core-js/es6/set'; 36 | 37 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 38 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 39 | 40 | /** IE10 and IE11 requires the following for the Reflect API. */ 41 | // import 'core-js/es6/reflect'; 42 | 43 | 44 | /** Evergreen browsers require these. **/ 45 | // Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove. 46 | 47 | 48 | /** 49 | * Web Animations `@angular/platform-browser/animations` 50 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 51 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 52 | **/ 53 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 54 | 55 | /** 56 | * By default, zone.js will patch all possible macroTask and DomEvents 57 | * user can disable parts of macroTask/DomEvents patch by setting following flags 58 | */ 59 | 60 | // (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 61 | // (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 62 | // (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 63 | 64 | /* 65 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 66 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 67 | */ 68 | // (window as any).__Zone_enable_cross_context_check = true; 69 | 70 | /*************************************************************************************************** 71 | * Zone JS is required by default for Angular itself. 72 | */ 73 | import 'zone.js/dist/zone'; // Included with Angular CLI. 74 | 75 | 76 | 77 | /*************************************************************************************************** 78 | * APPLICATION IMPORTS 79 | */ 80 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "arrow-return-shorthand": true, 7 | "callable-types": true, 8 | "class-name": true, 9 | "comment-format": [ 10 | true, 11 | "check-space" 12 | ], 13 | "curly": true, 14 | "deprecation": { 15 | "severity": "warn" 16 | }, 17 | "eofline": true, 18 | "forin": true, 19 | "import-blacklist": [ 20 | true, 21 | "rxjs/Rx" 22 | ], 23 | "import-spacing": true, 24 | "indent": [ 25 | true, 26 | "spaces" 27 | ], 28 | "interface-over-type-literal": true, 29 | "label-position": true, 30 | "max-line-length": [ 31 | true, 32 | 140 33 | ], 34 | "member-access": false, 35 | "member-ordering": [ 36 | true, 37 | { 38 | "order": [ 39 | "static-field", 40 | "instance-field", 41 | "static-method", 42 | "instance-method" 43 | ] 44 | } 45 | ], 46 | "no-arg": true, 47 | "no-bitwise": true, 48 | "no-console": [ 49 | true, 50 | "debug", 51 | "info", 52 | "time", 53 | "timeEnd", 54 | "trace" 55 | ], 56 | "no-construct": true, 57 | "no-debugger": true, 58 | "no-duplicate-super": true, 59 | "no-empty": false, 60 | "no-empty-interface": true, 61 | "no-eval": true, 62 | "no-inferrable-types": [ 63 | true, 64 | "ignore-params" 65 | ], 66 | "no-misused-new": true, 67 | "no-non-null-assertion": true, 68 | "no-shadowed-variable": true, 69 | "no-string-literal": false, 70 | "no-string-throw": true, 71 | "no-switch-case-fall-through": true, 72 | "no-trailing-whitespace": true, 73 | "no-unnecessary-initializer": true, 74 | "no-unused-expression": true, 75 | "no-use-before-declare": true, 76 | "no-var-keyword": true, 77 | "object-literal-sort-keys": false, 78 | "one-line": [ 79 | true, 80 | "check-open-brace", 81 | "check-catch", 82 | "check-else", 83 | "check-whitespace" 84 | ], 85 | "prefer-const": true, 86 | "quotemark": [ 87 | true, 88 | "single" 89 | ], 90 | "radix": true, 91 | "semicolon": [ 92 | true, 93 | "always" 94 | ], 95 | "triple-equals": [ 96 | true, 97 | "allow-null-check" 98 | ], 99 | "typedef-whitespace": [ 100 | true, 101 | { 102 | "call-signature": "nospace", 103 | "index-signature": "nospace", 104 | "parameter": "nospace", 105 | "property-declaration": "nospace", 106 | "variable-declaration": "nospace" 107 | } 108 | ], 109 | "unified-signatures": true, 110 | "variable-name": false, 111 | "whitespace": [ 112 | true, 113 | "check-branch", 114 | "check-decl", 115 | "check-operator", 116 | "check-separator", 117 | "check-type" 118 | ], 119 | "no-output-on-prefix": true, 120 | "use-input-property-decorator": true, 121 | "use-output-property-decorator": true, 122 | "use-host-property-decorator": true, 123 | "no-input-rename": true, 124 | "no-output-rename": true, 125 | "use-life-cycle-interface": true, 126 | "use-pipe-transform-interface": true, 127 | "component-class-suffix": true, 128 | "directive-class-suffix": true 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/app/main/main.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 | 8 |
9 |
10 |
11 | 12 | 13 | 14 | 15 |
History
16 | 17 | 20 |
21 | 22 | 23 |
24 | 26 |
27 | 28 |
History
29 | 30 | 33 |
34 | 36 |
37 |
38 |

Result:

39 | 41 | First 42 | Aggregate 43 | 44 | 45 | 46 | 47 | 48 | 49 | 53 | 54 | 55 | 56 | 57 | 58 |

{{error}}

59 |

{{note}}

60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 |
75 |
76 |
77 | -------------------------------------------------------------------------------- /src/app/history/history.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, EventEmitter, Output } from '@angular/core'; 2 | import { withMetaData } from '../main/main.component'; 3 | import { DatePipe } from '@angular/common'; 4 | import { appSettings } from '../app-settings/app-settings.service'; 5 | import { EventModel } from '../model/event-model'; 6 | 7 | @Component({ 8 | selector: 'app-history', 9 | templateUrl: './history.component.html', 10 | styleUrls: ['./history.component.css'] 11 | }) 12 | export class HistoryComponent { 13 | Items: historyRecord[] = []; 14 | selectedItem: number; 15 | _rawEvents: withMetaData; 16 | constructor(private datepipe: DatePipe, private setting: appSettings) { 17 | this.Items = setting.GetHistoryRecords(); 18 | } 19 | @Output() 20 | loadFormHistory: EventEmitter = new EventEmitter(); 21 | @Input() 22 | set rawEvents(rawEvents: withMetaData) { 23 | this.selectedItem = null; 24 | this._rawEvents = rawEvents; 25 | if (rawEvents && rawEvents.value && rawEvents.value.length > 0) { 26 | if (!this.Items.find(x => x.traceId === rawEvents.traceId && x.result.length === rawEvents.value.length)) { 27 | const item = { traceId: rawEvents.traceId, result: rawEvents.value } as historyRecord; 28 | const root = item.result.find(x => !x.tracer.parentSpanId); 29 | if (root) { 30 | if (root.tracer.timestamp > 0) { 31 | item.startedAt = this.datepipe.transform(new Date(root.tracer.timestamp / 1000), 'yyyy-MM-dd HH:MM'); 32 | } 33 | item.action = root.tracer.action; 34 | item.error = root.tracer.error; 35 | 36 | } 37 | this.Items.unshift(item); 38 | 39 | if (this.Items.length > 300) { 40 | this.Items.splice(300, 1); 41 | } 42 | try { 43 | this.setting.SetHistoryRecords(this.Items); 44 | } catch (ex) { 45 | 46 | try { 47 | // try to reduce the item in history and save again 48 | if (this.Items.length > 100) { 49 | this.Items.splice(100, this.Items.length - 100); 50 | this.setting.SetHistoryRecords(this.Items); 51 | } 52 | } catch (error) { 53 | console.warn('fail to save history', error); 54 | } 55 | 56 | } 57 | 58 | 59 | } else { 60 | this.selectedItem = this.Items.findIndex(x => x.traceId === rawEvents.traceId); 61 | } 62 | 63 | } 64 | } 65 | 66 | load(event: historyRecord) { 67 | this.loadFormHistory.next(event); 68 | } 69 | 70 | delete(event: historyRecord) { 71 | const index = this.Items.indexOf(event); 72 | if (index >= 0) { 73 | this.Items.splice(index, 1); 74 | 75 | this.setting.SetHistoryRecords(this.Items); 76 | } 77 | } 78 | edit(event: historyRecord) { 79 | const name = prompt(`Please enter name for traceId ${event.traceId}`); 80 | 81 | if (name && name !== '') { 82 | event.name = name; 83 | this.setting.SetHistoryRecords(this.Items); 84 | } 85 | } 86 | 87 | text_truncate = function (str, length, ending) { 88 | if (length == null) { 89 | length = 100; 90 | } 91 | if (ending == null) { 92 | ending = '...'; 93 | } 94 | if (str && str.length > length) { 95 | return str.substring(0, length - ending.length) + ending; 96 | } else { 97 | return str; 98 | } 99 | }; 100 | 101 | 102 | } 103 | 104 | // tslint:disable-next-line: class-name 105 | export class historyRecord { 106 | error: string; 107 | action: string; 108 | name: string; 109 | traceId: string; 110 | result: EventModel[]; 111 | createDate: Date; 112 | startedAt: string; 113 | } 114 | 115 | -------------------------------------------------------------------------------- /src/app/tracing-provider/zipkin-provider.service.ts: -------------------------------------------------------------------------------- 1 | import { environment } from 'src/environments/environment'; 2 | import { EventModel, Direction } from '../model/event-model'; 3 | import { Injectable } from '@angular/core'; 4 | import { TracingProvider } from './tracingProvider'; 5 | import { HttpClient, HttpErrorResponse } from '@angular/common/http'; 6 | interface Dictionary { 7 | [Key: string]: T; 8 | } 9 | 10 | @Injectable({ 11 | providedIn: 'root' 12 | }) 13 | 14 | export class ZipkinProviderService implements TracingProvider { 15 | GetName(): string { 16 | return 'zipkin'; 17 | } 18 | 19 | constructor(private httpClient: HttpClient) { } 20 | 21 | async Get(traceId: string): Promise { 22 | const url = `${environment.tracingProvider.url}/api/v2/trace/${traceId}`; 23 | try { 24 | const zipkinResponse = await this.httpClient.get(url).toPromise(); 25 | const results = zipkinResponse.map(x => this.Convert(x)); 26 | 27 | 28 | return results; 29 | } catch (error) { 30 | const ex = error as HttpErrorResponse; 31 | if (ex.status === 404) { 32 | return null; 33 | } 34 | throw error; 35 | } 36 | 37 | } 38 | Convert(zipkinSpan: Zipkin): EventModel { 39 | 40 | const result: EventModel = new EventModel(); 41 | Object.keys(zipkinSpan).forEach(property => { 42 | result[property] = zipkinSpan[property]; 43 | }); 44 | 45 | result.tracer.action = zipkinSpan.name; 46 | result.tracer.traceId = zipkinSpan.traceId; 47 | result.tracer.durationMs = zipkinSpan.duration && zipkinSpan.duration / 1000; 48 | result.tracer.parentSpanId = zipkinSpan.parentId; 49 | result.tracer.spanId = zipkinSpan.id; 50 | result.tracer.error = zipkinSpan['tags'] && zipkinSpan['tags']['error']; 51 | result.tracer.timestamp = zipkinSpan.timestamp && zipkinSpan.timestamp; 52 | result.tracer.from.name = zipkinSpan.localEndpoint && (zipkinSpan.localEndpoint.serviceName || zipkinSpan.localEndpoint['ipv4']); 53 | result.tracer.to.name = zipkinSpan.remoteEndpoint && (zipkinSpan.remoteEndpoint.serviceName 54 | || zipkinSpan.remoteEndpoint['ipv4']); 55 | // add ip4 extra 56 | switch (zipkinSpan.kind) { 57 | case 'CLIENT': result.tracer.direction = 0; break; 58 | case 'SERVER': result.tracer.direction = 1; break; 59 | case 'PRODUCER': result.tracer.direction = 2; break; 60 | case 'CONSUMER': result.tracer.direction = 3; break; 61 | // LOG 62 | case undefined: 63 | if (!result.tracer.parentSpanId) { 64 | result.tracer.parentSpanId = result.tracer.spanId; 65 | } 66 | 67 | if (!result.tracer.to.name) { 68 | result.tracer.direction = Direction.ActionStart; 69 | result.tracer.to.name = result.tracer.from.name; 70 | result['isLog'] = true; 71 | 72 | } else { 73 | result.tracer.direction = Direction.LogicalTransactionStart; 74 | } 75 | break; 76 | } 77 | return result; 78 | } 79 | 80 | } 81 | 82 | 83 | 84 | // read more about the zapkin api https://zipkin.io/zipkin-api/#/default/get_trace__traceId_ 85 | export interface Zipkin { 86 | id: string; 87 | traceId: string; 88 | parentId: string; 89 | name: string; 90 | timestamp: number; 91 | duration: number; 92 | kind: string; // [ CLIENT, SERVER, PRODUCER, CONSUMER ] 93 | localEndpoint: Endpoint; 94 | remoteEndpoint: Endpoint; 95 | annotations: Annotation; 96 | shared: boolean; 97 | } 98 | 99 | export interface Annotation { 100 | timestamp: number; 101 | value: string; 102 | } 103 | 104 | export interface Endpoint { 105 | serviceName: string; 106 | ipv4: string; 107 | ipv6: string; 108 | port: number; 109 | } 110 | 111 | -------------------------------------------------------------------------------- /src/app/menu/menu.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core'; 2 | import { saveAs } from 'file-saver'; 3 | import { withMetaData as withMetaData } from '../main/main.component'; 4 | import { DatePipe } from '@angular/common'; 5 | import { environment } from 'src/environments/environment'; 6 | import { appSettings } from '../app-settings/app-settings.service'; 7 | import { EventModel } from '../model/event-model'; 8 | 9 | @Component({ 10 | selector: 'app-menu', 11 | templateUrl: './menu.component.html', 12 | styleUrls: ['./menu.component.css'] 13 | }) 14 | export class MenuComponent implements OnInit { 15 | _rawEvents: withMetaData; 16 | linksRaw: Link[] = []; 17 | links: Link[]; 18 | @Input() 19 | set rawEvents(rawEvents: withMetaData) { 20 | this._rawEvents = rawEvents; 21 | this.EnableSave = rawEvents && rawEvents.value && rawEvents.value.length > 0; 22 | this.updateLinkList(); 23 | } 24 | isExpanded = false; 25 | @Output() 26 | FileOpenEvent: EventEmitter = new EventEmitter(); 27 | EnableSave = false; 28 | 29 | @Output() 30 | ClickEvent: EventEmitter = new EventEmitter(); 31 | constructor(private dateFormat: DatePipe, private setting: appSettings) { } 32 | 33 | ngOnInit() { 34 | this.linksRaw = environment.links.map(x => x as Link); 35 | } 36 | saveFile() { 37 | if (this.EnableSave) { 38 | const deepClone: EventModel[] = JSON.parse(JSON.stringify(this._rawEvents.value)); 39 | // Remove metadata 40 | deepClone.forEach(element => { 41 | delete element.tracer['metadata']; 42 | delete element.tracer.to.nickName; 43 | delete element.tracer.from.nickName; 44 | }); 45 | 46 | const myObjStr: string = JSON.stringify(deepClone, null, 2); 47 | const blob = new Blob([myObjStr], { type: 'text/plain;charset=utf-8' }); 48 | saveAs(blob, this._rawEvents.traceId + '.json'); 49 | } 50 | } 51 | 52 | openFile(event: any) { 53 | const input = event.target; 54 | if (event && event.target && event.target.files && event.target.files[0] && event.target.files[0].name) { 55 | const reader = new FileReader(); 56 | // tslint:disable-next-line: no-use-before-declare 57 | const result = new FileEvent(); 58 | const name = event.target.files[0].name; 59 | result.name = name; 60 | reader.onloadend = () => { 61 | // this 'text' is the content of the file 62 | const text = reader.result; 63 | result.content = text.toString(); 64 | 65 | this.FileOpenEvent.emit(result); 66 | }; 67 | reader.readAsText(input.files[0]); 68 | input.value = ''; 69 | } 70 | } 71 | 72 | ChangeHistory() { 73 | this.ClickEvent.emit({name: 'history'} as MenuClickEvent); 74 | } 75 | updateLinkList() { 76 | if (this.linksRaw && this.linksRaw.length > 0 && this._rawEvents && this._rawEvents.traceId 77 | && this._rawEvents.startTime && this._rawEvents.startTime.getTime().toString() !== 'NaN' 78 | && this._rawEvents.endTime && this._rawEvents.endTime.getTime().toString() !== 'NaN') { 79 | this.links = this.linksRaw.map(x => ({ 80 | name: x.name, link: x.link 81 | .replace('{traceId}', this._rawEvents.traceId.toString()) 82 | .replace('{startDate}', this.dateFormat.transform(this.addSecund(this._rawEvents.startTime || new Date(), -10), 'yyyy-MM-dd\'T\'HH:mm:ss')) 83 | .replace('{endDate}', this.dateFormat.transform(this.addSecund(this._rawEvents.endTime || new Date(), 10), 'yyyy-MM-dd\'T\'HH:mm:ss')) 84 | }) as Link); 85 | } else { 86 | this.links = []; 87 | } 88 | } 89 | 90 | 91 | addSecund(date: Date, secund: number) { 92 | return new Date(date.getTime() + (secund * 1000)); 93 | 94 | } 95 | 96 | 97 | 98 | SaveLayout() { 99 | this.setting.save(); 100 | } 101 | ResetLayout() { 102 | this.setting.clear(); 103 | } 104 | } 105 | export class FileEvent { 106 | content: string; 107 | name: string; 108 | } 109 | 110 | export class MenuClickEvent { 111 | name: string; 112 | } 113 | export class Link { 114 | link: string; 115 | name: string; 116 | } 117 | 118 | export class Config { 119 | links: Link[]; 120 | } 121 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 2 | You want to contribute to Tracer? Welcome! Please read this document to understand what you can do: 3 | * [Analyze Issues](#analyze-issues) 4 | * [Report an Issue](#report-an-issue) 5 | * [Contribute Code](#contribute-code) 6 | 7 | 8 | 9 | 10 | ## Analyze Issues 11 | 12 | Analyzing issue reports can be a lot of effort. Any help is welcome! 13 | Go to [the Github issue tracker](https://github.com/SAP/Tracer/issues?state=open) and find an open issue which needs additional work or a bugfix. 14 | 15 | 16 | 17 | ## Report an Issue 18 | 19 | If you find a bug - behavior of Tracer code contradicting its specification - you are welcome to report it. 20 | 21 | Once you have familiarized with the guidelines, you can go to the [Github issue tracker for Tracer](https://github.com/SAP/Tracer/issues/new) to report the issue. 22 | 23 | ### Quick Checklist for Bug Reports 24 | 25 | Issue report checklist: 26 | * Real, current bug 27 | * No duplicate 28 | * Reproducible 29 | * Good summary 30 | * Well-documented 31 | * Minimal example 32 | * Use the [template](ISSUE_TEMPLATE.md) 33 | 34 | 35 | ### Issue handling process 36 | 37 | When an issue is reported, a committer will look at it and either confirm it as a real issue (by giving the "in progress" label), close it if it is not an issue, or ask for more details. In-progress issues are then either assigned to a committer in GitHub, reported in our internal issue handling system, or left open as "contribution welcome" for easy or not urgent fixes. 38 | 39 | # Contribute Code 40 | 41 | You are welcome to contribute code to Tracer in order to fix bugs or to implement new features. 42 | 43 | There are three important things to know: 44 | 45 | 1. You must be aware of the Apache License (which describes contributions) and **agree to the Contributors License Agreement**. This is common practice in all major Open Source projects. To make this process as simple as possible, we are using *[CLA assistant](https://cla-assistant.io/)* for individual contributions. CLA assistant is an open source tool that integrates with GitHub very well and enables a one-click-experience for accepting the CLA. For company contributers special rules apply. See the respective section below for details. 46 | 2. There are **several requirements regarding code style, quality, and product standards** which need to be met (we also have to follow them). The respective section below gives more details on the coding guidelines. 47 | 3. **Not all proposed contributions can be accepted**. Some features may e.g. just fit a third-party add-on better. The code must fit the overall direction of Tracer and really improve it, so there should be some "bang for the byte". For most bug fixes this is a given, but major feature implementation first need to be discussed with one of the Tracer committers (the top 20 or more of the [Contributors List](https://github.com/SAP/Tracer/graphs/contributors)), possibly one who touched the related code recently. The more effort you invest, the better you should clarify in advance whether the contribution fits: the best way would be to just open an enhancement ticket in the issue tracker to discuss the feature you plan to implement (make it clear you intend to contribute). We will then forward the proposal to the respective code owner, this avoids disappointment. 48 | 49 | ### Contributor License Agreement 50 | 51 | When you contribute (code, documentation, or anything else), you have to be aware that your contribution is covered by the same [Apache 2.0 License](http://www.apache.org/licenses/LICENSE-2.0) that is applied to Tracer itself. 52 | In particular you need to agree to the Individual Contributor License Agreement, 53 | which can be [found here](https://gist.github.com/CLAassistant/bd1ea8ec8aa0357414e8). 54 | (This applies to all contributors, including those contributing on behalf of a company). If you agree to its content, you simply have to click on the link posted by the CLA assistant as a comment to the pull request. Click it to check the CLA, then accept it on the following screen if you agree to it. CLA assistant will save this decision for upcoming contributions and will notify you if there is any change to the CLA in the meantime. 55 | 56 | 57 | ## Company Contributors 58 | 59 | If employees of a company contribute code, in addition to the individual agreement above, there needs to be one company agreement submitted. This is mainly for the protection of the contributing employees. 60 | 61 | A company representative authorized to do so needs to download, fill, and print the [Corporate Contributor License Agreement]({REPLACE WITH THE URL TO THE SAP CCLA FILE STORED IN YOUR REPOSITORY}) form. Then either: 62 | 63 | * Scan it and e-mail it to opensource@sap.com 64 | * Fax it to: +49 6227 78-45813 65 | * Send it by traditional letter to: OSPO Core, Dietmar-Hopp-Allee 16, 69190 Walldorf, Germany 66 | 67 | The form contains a list of employees who are authorized to contribute on behalf of your company. When this list changes, please let us know. 68 | -------------------------------------------------------------------------------- /src/app/tag-table/tag-table.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | import { MatTableDataSource } from '@angular/material/table'; 3 | import { SelectionModel, SelectionChange } from '@angular/cdk/collections'; 4 | import { appSettings } from '../app-settings/app-settings.service'; 5 | 6 | @Component({ 7 | selector: 'app-tag-table', 8 | templateUrl: './tag-table.component.html', 9 | styleUrls: ['./tag-table.component.css'] 10 | }) 11 | export class TagTableComponent { 12 | 13 | manualSelection: string[] = []; 14 | _dataSource: any[] = []; 15 | @Input() 16 | set defaultField(defaultFieldToDisplay: string[]) { 17 | this.manualSelection = defaultFieldToDisplay ? defaultFieldToDisplay : []; 18 | } 19 | 20 | @Input() 21 | set dataSource(events: any[]) { 22 | 23 | if (events && events.length > 0) { 24 | this.updateTable(events); 25 | } else { 26 | this._dataSource = []; 27 | } 28 | } 29 | 30 | displayedColumnsSave: string[]; 31 | displayedColumns: string[] = []; 32 | displayedColumns_fields: string[] = ['select', 'name']; 33 | dataSource_fields: MatTableDataSource = new MatTableDataSource([]); 34 | selection = new SelectionModel(true, []); 35 | columns: object[] = []; 36 | 37 | constructor(private settings: appSettings) { 38 | // this.selection.changed.subscribe(this.OnFieldChange); 39 | this.displayedColumnsSave = settings.GetSelectedFelids(); 40 | this.selection.changed.subscribe(changeEvent => this.OnFieldChange(changeEvent)); 41 | 42 | } 43 | 44 | OnFieldChange(changeEvent: SelectionChange) { 45 | let temp = this.displayedColumns; 46 | changeEvent.added.forEach(add => temp.push(add.name)); 47 | changeEvent.removed.forEach(remove => temp = temp.filter(item => item !== remove.name)); 48 | this.settings.SetSelectedFelids(temp); 49 | this.displayedColumnsSave = temp; 50 | this.displayedColumns = temp; 51 | } 52 | 53 | applyFilter(filterValue: string) { 54 | this.dataSource_fields.filter = filterValue.trim().toLowerCase(); 55 | } 56 | 57 | updateTable(rawEvnets: any[]) { 58 | const flattenData = rawEvnets.map(e => this.flattenObject(e)); 59 | const allFields = this.GetAllFields(flattenData); 60 | this.GenarateColumns(allFields); 61 | const values = this.GenarateValues(allFields, flattenData); 62 | 63 | this._dataSource = values; 64 | // tslint:disable-next-line: no-use-before-declare 65 | const fields = allFields.map(x => new felid(x)); 66 | this.dataSource_fields.data = fields; 67 | 68 | 69 | this.displayedColumns = []; 70 | 71 | this.displayedColumnsSave.forEach(old => { 72 | const selectFelid = this.dataSource_fields.data.findIndex(x => x.name === old); 73 | if (selectFelid >= 0) { 74 | this.selection.select(this.dataSource_fields.data[selectFelid]); 75 | } 76 | }); 77 | } 78 | 79 | public GetAllFields(flattenData: any[]): string[] { 80 | const tempFieldsContainer = []; 81 | flattenData.forEach(flatData => { 82 | Object.keys(flatData).forEach(field => tempFieldsContainer[field] = 1); 83 | }); 84 | const felids = Object.keys(tempFieldsContainer); 85 | 86 | return felids.sort(); 87 | 88 | } 89 | 90 | public GenarateValues(fields: string[], flatten: any[]) { 91 | const tableData: any[] = []; 92 | flatten.forEach(f => { 93 | const row: any = {}; 94 | fields.forEach(fe => { 95 | const value = f[fe]; 96 | if (value) { 97 | row[fe] = (value.toString()); 98 | } else { row[fe] = (''); } 99 | }); 100 | tableData.push(row); 101 | }); 102 | return tableData; 103 | } 104 | 105 | public GenarateColumns(fields: string[]) { 106 | const columns: object[] = []; 107 | fields.forEach(element => { 108 | let columnObj: object; 109 | columnObj = new function () { 110 | this.columnDef = element.toString(); 111 | this.header = element.toString(); 112 | this.cell = []; 113 | 114 | }; 115 | columns.push(columnObj); 116 | }); 117 | this.columns = columns; 118 | } 119 | 120 | public flattenObject(ob) { 121 | const toReturn = {}; 122 | 123 | for (const i in ob) { 124 | if (!ob.hasOwnProperty(i)) { continue; } 125 | 126 | // tslint:disable-next-line: triple-equals 127 | if ((typeof ob[i]) == 'object' && ob[i] !== null) { 128 | const flatObject = this.flattenObject(ob[i]); 129 | for (const x in flatObject) { 130 | if (!flatObject.hasOwnProperty(x)) { continue; } 131 | 132 | toReturn[i + '.' + x] = flatObject[x]; 133 | } 134 | } else { 135 | toReturn[i] = ob[i]; 136 | } 137 | } 138 | return toReturn; 139 | } 140 | } 141 | 142 | // tslint:disable-next-line: class-name 143 | export class felid { 144 | constructor(name: string) { 145 | this.name = name; 146 | } 147 | name: string; 148 | } 149 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "Web": { 7 | "root": "", 8 | "sourceRoot": "src", 9 | "projectType": "application", 10 | "prefix": "app", 11 | "schematics": {}, 12 | "architect": { 13 | "build": { 14 | "builder": "@angular-devkit/build-angular:browser", 15 | "options": { 16 | "progress": true, 17 | "extractCss": true, 18 | "outputPath": "dist", 19 | "index": "src/index.html", 20 | "main": "src/main.ts", 21 | "polyfills": "src/polyfills.ts", 22 | "tsConfig": "src/tsconfig.app.json", 23 | "assets": [ 24 | "src/assets" 25 | ], 26 | "styles": [ 27 | "node_modules/bootstrap/dist/css/bootstrap.min.css", 28 | "src/styles.css" 29 | ], 30 | "scripts": [ ] 31 | }, 32 | "configurations": { 33 | "production": { 34 | "fileReplacements": [ 35 | { 36 | "replace": "src/environments/environment.ts", 37 | "with": "src/environments/environment.prod.ts" 38 | }, 39 | { 40 | "replace": "src/environments/environment.docker.ts", 41 | "with": "src/environments/environment.prod.ts" 42 | } 43 | ], 44 | "optimization": true, 45 | "outputHashing": "all", 46 | "sourceMap": false, 47 | "extractCss": true, 48 | "namedChunks": false, 49 | "aot": true, 50 | "extractLicenses": true, 51 | "vendorChunk": false, 52 | "buildOptimizer": true 53 | } 54 | , 55 | "docker": { 56 | "fileReplacements": [ 57 | { 58 | "replace": "src/environments/environment.ts", 59 | "with": "src/environments/environment.docker.ts" 60 | }, 61 | { 62 | "replace": "src/environments/environment.prod.ts", 63 | "with": "src/environments/environment.docker.ts" 64 | } 65 | ], 66 | "optimization": true, 67 | "outputHashing": "all", 68 | "sourceMap": false, 69 | "extractCss": true, 70 | "namedChunks": false, 71 | "aot": true, 72 | "extractLicenses": true, 73 | "vendorChunk": false, 74 | "buildOptimizer": true 75 | } 76 | } 77 | }, 78 | "serve": { 79 | "builder": "@angular-devkit/build-angular:dev-server", 80 | "options": { 81 | "browserTarget": "Web:build", 82 | "port": 4200 83 | }, 84 | "configurations": { 85 | "production": { 86 | "browserTarget": "Web:build:production" 87 | } 88 | } 89 | }, 90 | "extract-i18n": { 91 | "builder": "@angular-devkit/build-angular:extract-i18n", 92 | "options": { 93 | "browserTarget": "Web:build" 94 | } 95 | }, 96 | "test": { 97 | "builder": "@angular-devkit/build-angular:karma", 98 | "options": { 99 | "main": "src/test.ts", 100 | "polyfills": "src/polyfills.ts", 101 | "tsConfig": "src/tsconfig.spec.json", 102 | "karmaConfig": "src/karma.conf.js", 103 | "styles": [ 104 | "styles.css" 105 | ], 106 | "scripts": [], 107 | "assets": [ 108 | "src/assets" 109 | ] 110 | } 111 | }, 112 | "lint": { 113 | "builder": "@angular-devkit/build-angular:tslint", 114 | "options": { 115 | "tsConfig": [ 116 | "src/tsconfig.app.json", 117 | "src/tsconfig.spec.json" 118 | ], 119 | "exclude": [ 120 | "**/node_modules/**" 121 | ] 122 | } 123 | }, 124 | "server": { 125 | "builder": "@angular-devkit/build-angular:server", 126 | "options": { 127 | "outputPath": "dist-server", 128 | "main": "src/main.ts", 129 | "tsConfig": "src/tsconfig.server.json" 130 | }, 131 | "configurations": { 132 | "dev": { 133 | "optimization": true, 134 | "outputHashing": "all", 135 | "sourceMap": false, 136 | "namedChunks": false, 137 | "extractLicenses": true, 138 | "vendorChunk": true 139 | }, 140 | "production": { 141 | "optimization": true, 142 | "outputHashing": "all", 143 | "sourceMap": false, 144 | "namedChunks": false, 145 | "extractLicenses": true, 146 | "vendorChunk": false 147 | } 148 | } 149 | } 150 | } 151 | }, 152 | "Web-e2e": { 153 | "root": "e2e/", 154 | "projectType": "application", 155 | "architect": { 156 | "e2e": { 157 | "builder": "@angular-devkit/build-angular:protractor", 158 | "options": { 159 | "protractorConfig": "e2e/protractor.conf.js", 160 | "devServerTarget": "Web:serve" 161 | } 162 | }, 163 | "lint": { 164 | "builder": "@angular-devkit/build-angular:tslint", 165 | "options": { 166 | "tsConfig": "e2e/tsconfig.e2e.json", 167 | "exclude": [ 168 | "**/node_modules/**" 169 | ] 170 | } 171 | } 172 | } 173 | } 174 | }, 175 | "defaultProject": "Web" 176 | } 177 | -------------------------------------------------------------------------------- /src/app/app-settings/app-settings.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { environment } from 'src/environments/environment'; 3 | import { historyRecord } from '../history/history.component'; 4 | import { HistoryExample } from './history-example'; 5 | import { Location } from '@angular/common'; 6 | import { ActivatedRoute } from '@angular/router'; 7 | 8 | @Injectable({ 9 | providedIn: 'root' 10 | }) 11 | export class appSettings { 12 | 13 | 14 | private HistoryRecords: historyRecord[]; 15 | private DefaultSettings: Settings; 16 | private UrlSettings: Settings; 17 | 18 | constructor(private location: Location, private route: ActivatedRoute) { 19 | 20 | const settings = localStorage.getItem('settings'); 21 | 22 | try { 23 | this.DefaultSettings = JSON.parse(settings); 24 | } catch { } 25 | finally { 26 | if (!this.DefaultSettings) { 27 | this.DefaultSettings = {} as Settings; 28 | } 29 | } 30 | 31 | try { 32 | 33 | this.UrlSettings = {} as Settings; 34 | 35 | this.route.queryParams.subscribe(params => { 36 | 37 | this.UrlSettings.traceId = params['traceId']; 38 | this.UrlSettings.SelectedTabIndex = params['SelectedTab']; 39 | 40 | const openHistory = params['OpenHistory']; 41 | 42 | if (openHistory === 'true') { 43 | this.UrlSettings.HistoryOpenDefaultPosition = true; 44 | } 45 | if (openHistory === 'false') { 46 | this.UrlSettings.HistoryOpenDefaultPosition = false; 47 | } 48 | 49 | const stickyTags: string = params['StickyTags']; 50 | if (stickyTags) { 51 | this.UrlSettings.StickyTags = stickyTags.split(','); 52 | } 53 | const SelectedFelids: string = params['SelectedFelids']; 54 | if (SelectedFelids) { 55 | this.UrlSettings.SelectedFelids = SelectedFelids.split(','); 56 | } 57 | 58 | 59 | }); 60 | 61 | } catch { } 62 | finally { 63 | if (!this.UrlSettings) { 64 | this.UrlSettings = {} as Settings; 65 | } 66 | } 67 | 68 | } 69 | 70 | public save() { 71 | localStorage.setItem('settings', JSON.stringify(this.DefaultSettings)); 72 | } 73 | 74 | public saveLocal() { 75 | 76 | let query: string; 77 | query = this.UrlSettings.traceId ? `?traceId=${this.UrlSettings.traceId}` : ``; 78 | query += this.UrlSettings.HistoryOpenDefaultPosition !== undefined ? `&OpenHistory=${this.UrlSettings.HistoryOpenDefaultPosition}` : ``; 79 | query += this.UrlSettings.SelectedTabIndex ? `&SelectedTab=${this.UrlSettings.SelectedTabIndex}` : ``; 80 | query += this.UrlSettings.StickyTags ? `&StickyTags=${this.UrlSettings.StickyTags.join(`,`)}` : ``; 81 | query += this.UrlSettings.SelectedFelids ? `&SelectedFelids=${this.UrlSettings.SelectedFelids.join(`,`)}` : ``; 82 | 83 | this.location.replaceState('/' + query); 84 | } 85 | 86 | public clear() { 87 | localStorage.removeItem('settings'); 88 | this.location.replaceState(''); 89 | 90 | window.location.reload(); 91 | 92 | // Reload the page 93 | } 94 | 95 | public GetHistoryRecords(): historyRecord[] { 96 | if (!this.HistoryRecords) { 97 | 98 | const history = localStorage.getItem('historyRecords1'); 99 | try { 100 | this.HistoryRecords = JSON.parse(history); 101 | } catch (error) { 102 | 103 | } 104 | if (!this.HistoryRecords) { 105 | this.HistoryRecords = JSON.parse(JSON.stringify(new HistoryExample().historyRecord)); 106 | } 107 | } 108 | return this.HistoryRecords; 109 | } 110 | 111 | public SetHistoryRecords(historyRecords: historyRecord[]) { 112 | this.HistoryRecords = historyRecords; 113 | localStorage.setItem('historyRecords1', JSON.stringify(this.HistoryRecords)); 114 | } 115 | 116 | public GetStickyTags() { 117 | return this.UrlSettings.StickyTags || this.DefaultSettings.StickyTags || environment.defaultStickyTags; 118 | } 119 | 120 | public SetStickyTags(stickyTags: string[]) { 121 | this.UrlSettings.StickyTags = stickyTags; 122 | this.DefaultSettings.StickyTags = stickyTags; 123 | this.saveLocal(); 124 | } 125 | 126 | public GetHistoryOpenDefaultPosition() { 127 | if (this.UrlSettings.HistoryOpenDefaultPosition !== undefined) { 128 | return this.UrlSettings.HistoryOpenDefaultPosition; 129 | } 130 | if (this.DefaultSettings.HistoryOpenDefaultPosition !== undefined) { 131 | return this.DefaultSettings.HistoryOpenDefaultPosition; 132 | } 133 | return true; 134 | } 135 | 136 | public SetHistoryOpenDefaultPosition(historyOpenDefaultPosition: boolean) { 137 | this.UrlSettings.HistoryOpenDefaultPosition = historyOpenDefaultPosition; 138 | this.DefaultSettings.HistoryOpenDefaultPosition = historyOpenDefaultPosition; 139 | this.saveLocal(); 140 | } 141 | 142 | public GetSelectedTabIndex() { 143 | return this.UrlSettings.SelectedTabIndex || this.DefaultSettings.SelectedTabIndex || 0; 144 | } 145 | 146 | public SetSelectedTabIndex(selectedTabIndex: number) { 147 | this.UrlSettings.SelectedTabIndex = selectedTabIndex; 148 | this.DefaultSettings.SelectedTabIndex = selectedTabIndex; 149 | this.saveLocal(); 150 | } 151 | 152 | public GetSelectedFelids() { 153 | return this.UrlSettings.SelectedFelids || this.DefaultSettings.SelectedFelids || 154 | ['tracer.traceId', 'tracer.action', 'tracer.from.name', 'tracer.to.name']; 155 | } 156 | 157 | public SetSelectedFelids(selectedFelids: string[]) { 158 | this.UrlSettings.SelectedFelids = selectedFelids; 159 | this.DefaultSettings.SelectedFelids = selectedFelids; 160 | this.saveLocal(); 161 | } 162 | 163 | public GetTraceId(): string { 164 | return this.UrlSettings.traceId; 165 | } 166 | 167 | public SetTraceId(traceId: string) { 168 | this.UrlSettings.traceId = traceId; 169 | this.saveLocal(); 170 | 171 | } 172 | 173 | // tslint:disable-next-line: member-ordering 174 | searchType: string = `1`; 175 | SetSearchType(searchType: string) { 176 | this.searchType = searchType; 177 | } 178 | 179 | getSearchType() { 180 | return this.searchType; } 181 | 182 | 183 | } 184 | export class Settings { 185 | StickyTags: string[]; 186 | HistoryOpenDefaultPosition: boolean; 187 | SelectedFelids: string[]; 188 | SelectedTabIndex: number; 189 | traceId: string; 190 | } 191 | -------------------------------------------------------------------------------- /src/app/sticky-note/sticky-note.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, ViewChild, ElementRef } from '@angular/core'; 2 | import { MatChipInputEvent } from '@angular/material/chips'; 3 | import { COMMA, ENTER } from '@angular/cdk/keycodes'; 4 | import { EventModel } from '../model/event-model'; 5 | import { MatAutocompleteSelectedEvent, MatAutocomplete } from '@angular/material/autocomplete'; 6 | import { MatTooltip } from '@angular/material/tooltip'; 7 | import { appSettings } from '../app-settings/app-settings.service'; 8 | 9 | @Component({ 10 | selector: 'app-sticky-note', 11 | templateUrl: './sticky-note.component.html', 12 | styleUrls: ['./sticky-note.component.css'] 13 | }) 14 | export class StickyNoteComponent { 15 | private flattenEvents: any[]; 16 | private defaultAutoComplete: string[]; 17 | public tags: StickyNote[]; 18 | private notFilteredAutoComplete: string[]; 19 | public autoComplete: string[]; 20 | constructor( 21 | private settings: appSettings, 22 | ) { 23 | this.defaultAutoComplete = settings.GetStickyTags(); 24 | this.tags = this.defaultAutoComplete.map(x => ({ key: x } as StickyNote)); 25 | } 26 | 27 | @Input() 28 | set events(events: EventModel[]) { 29 | // Set default 30 | this.autoComplete = this.defaultAutoComplete; 31 | this.flattenEvents = []; 32 | this.cleanTagValues(); 33 | 34 | if (events && events.length > 0) { 35 | this.flattenEvents = events.map(x => this.flattenObject(x)); 36 | this.autoCompleteExtractTags(); 37 | this.notFilteredAutoComplete = this.autoComplete; 38 | 39 | this.tags.forEach(tag => { 40 | this.addTag(tag); 41 | }); 42 | } 43 | } 44 | 45 | @ViewChild('input', { static: false }) stickyInput: ElementRef; 46 | @ViewChild('auto', { static: false }) matAutocomplete: MatAutocomplete; 47 | 48 | visible = true; 49 | selectable = true; 50 | removable = true; 51 | addOnBlur = true; 52 | readonly separatorKeysCodes: number[] = [ENTER, COMMA]; 53 | 54 | private cleanTagValues() { 55 | this.tags.forEach(x => { 56 | x.rowValue = null; 57 | x.value = null; 58 | x.sortValue = null; 59 | }); 60 | } 61 | 62 | private autoCompleteExtractTags() { 63 | const AutoCompleteTags = {}; 64 | this.flattenEvents.forEach(event => { 65 | const keys = Object.keys(event); 66 | keys.forEach(field => { 67 | if (event[field]) { AutoCompleteTags[field] = 1; } 68 | } 69 | ); 70 | }); 71 | this.autoComplete = Object.keys(AutoCompleteTags); 72 | } 73 | 74 | // TODO: all tag should use regex search or some settings, indention when regex not valid 75 | public GetTagToSearch(tag: StickyNote) { 76 | return this.notFilteredAutoComplete.filter(x => { 77 | try { 78 | 79 | const regexp = new RegExp(tag.key); 80 | return regexp.test(x); 81 | 82 | } catch (error) { 83 | } 84 | 85 | return x === tag.key; 86 | }); 87 | } 88 | 89 | public addTag(tag: StickyNote) { 90 | const tags = this.GetTagToSearch(tag); 91 | 92 | tags.forEach(tagCandidate => { 93 | this.flattenEvents.forEach(event => { 94 | const field = event[tagCandidate]; 95 | if (field) { 96 | if (tag.value) { 97 | // Compare to hashTable inside hashTable 98 | tag.rowValue.push(field); 99 | tag.rowValue = Array.from(new Set(tag.rowValue.map((item: any) => item))); 100 | tag.value = tag.rowValue.join(','); 101 | tag.sortValue = this.text_truncate(tag.value, 70, '...'); 102 | } else { 103 | tag.value = field; 104 | tag.sortValue = this.text_truncate(tag.value, 70, '...'); 105 | tag.rowValue = [field]; 106 | } 107 | } 108 | }); 109 | }); 110 | } 111 | 112 | public selectAutoCompleteValue(event: MatAutocompleteSelectedEvent): void { 113 | this.stickyInput.nativeElement.value = event.option.value; 114 | } 115 | 116 | public addChip(event: MatChipInputEvent): void { 117 | 118 | if (this.matAutocomplete.isOpen === false) { 119 | const input = event.input; 120 | const value = event.value; 121 | this.innerAdd(value); 122 | 123 | // Reset the input value 124 | if (input) { 125 | input.value = ''; 126 | } 127 | } 128 | } 129 | 130 | public OnChangeKey(search: string) { 131 | if (search) { 132 | this.autoComplete = this.notFilteredAutoComplete.filter(x => x.includes(search)); 133 | } else { 134 | this.autoComplete = this.notFilteredAutoComplete; 135 | } 136 | } 137 | 138 | private innerAdd(value: string) { 139 | // Add 140 | const addItem = value.trim(); 141 | 142 | if (addItem) { 143 | const item = { key: addItem } as StickyNote; 144 | this.addTag(item); 145 | this.tags.push(item); 146 | this.settings.SetStickyTags(this.tags.map(x => x.key)); 147 | } 148 | } 149 | 150 | public remove(tag: StickyNote): void { 151 | const index = this.tags.indexOf(tag); 152 | if (index >= 0) { 153 | this.tags.splice(index, 1); 154 | this.settings.SetStickyTags( this.tags.map(x => x.key)); 155 | } 156 | } 157 | 158 | private flattenObject(ob) { 159 | const toReturn = {}; 160 | 161 | for (const i in ob) { 162 | if (!ob.hasOwnProperty(i)) { continue; } 163 | 164 | // tslint:disable-next-line: triple-equals 165 | if ((typeof ob[i]) == 'object' && ob[i] !== null) { 166 | const flatObject = this.flattenObject(ob[i]); 167 | for (const x in flatObject) { 168 | if (!flatObject.hasOwnProperty(x)) { continue; } 169 | toReturn[i + '.' + x] = flatObject[x]; 170 | } 171 | } else { 172 | toReturn[i] = ob[i]; 173 | } 174 | } 175 | return toReturn; 176 | } 177 | 178 | copyMessage(val: string) { 179 | const selBox = document.createElement('textarea'); 180 | selBox.style.position = 'fixed'; 181 | selBox.style.left = '0'; 182 | selBox.style.top = '0'; 183 | selBox.style.opacity = '0'; 184 | selBox.value = val; 185 | document.body.appendChild(selBox); 186 | selBox.focus(); 187 | selBox.select(); 188 | document.execCommand('copy'); 189 | document.body.removeChild(selBox); 190 | } 191 | 192 | text_truncate = function (str, length, ending) { 193 | if (length == null) { 194 | length = 100; 195 | } 196 | if (ending == null) { 197 | ending = '...'; 198 | } 199 | if (str.length > length) { 200 | return str.substring(0, length - ending.length) + ending; 201 | } else { 202 | return str; 203 | } 204 | }; 205 | 206 | // tslint:disable-next-line: member-ordering 207 | @ViewChild('tp', { static: false }) _matTooltip: MatTooltip; 208 | } 209 | 210 | class StickyNote { 211 | key: string; 212 | value: string; 213 | sortValue: string; 214 | rowValue: string[]; 215 | } 216 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | import { FormsModule } from '@angular/forms'; 4 | import { HttpClientModule } from '@angular/common/http'; 5 | import { RouterModule } from '@angular/router'; 6 | 7 | import { AppComponent } from './app.component'; 8 | import { MenuComponent } from './menu/menu.component'; 9 | import { SequenceDiagramComponent } from './sequence-diagram/sequence-diagram.component'; 10 | import { DatePipe } from '@angular/common'; 11 | 12 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 13 | import { MatExpansionModule } from '@angular/material/expansion'; 14 | import { A11yModule } from '@angular/cdk/a11y'; 15 | import { DragDropModule } from '@angular/cdk/drag-drop'; 16 | import { PortalModule } from '@angular/cdk/portal'; 17 | import { ScrollingModule } from '@angular/cdk/scrolling'; 18 | import { CdkStepperModule } from '@angular/cdk/stepper'; 19 | import { CdkTableModule } from '@angular/cdk/table'; 20 | import { CdkTreeModule } from '@angular/cdk/tree'; 21 | import { MatAutocompleteModule } from '@angular/material/autocomplete'; 22 | import { MatBadgeModule } from '@angular/material/badge'; 23 | import { MatBottomSheetModule } from '@angular/material/bottom-sheet'; 24 | import { MatButtonModule } from '@angular/material/button'; 25 | import { MatButtonToggleModule } from '@angular/material/button-toggle'; 26 | import { MatCardModule } from '@angular/material/card'; 27 | import { MatCheckboxModule } from '@angular/material/checkbox'; 28 | import { MatChipsModule } from '@angular/material/chips'; 29 | import { MatStepperModule } from '@angular/material/stepper'; 30 | import { MatDatepickerModule } from '@angular/material/datepicker'; 31 | import { MatDialogModule } from '@angular/material/dialog'; 32 | import { MatDividerModule } from '@angular/material/divider'; 33 | import { MatGridListModule } from '@angular/material/grid-list'; 34 | import { MatIconModule } from '@angular/material/icon'; 35 | import { MatInputModule } from '@angular/material/input'; 36 | import { MatListModule } from '@angular/material/list'; 37 | import { MatMenuModule } from '@angular/material/menu'; 38 | import { MatNativeDateModule, MatRippleModule } from '@angular/material/core'; 39 | import { MatPaginatorModule } from '@angular/material/paginator'; 40 | import { MatProgressBarModule } from '@angular/material/progress-bar'; 41 | import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; 42 | import { MatRadioModule } from '@angular/material/radio'; 43 | import { MatSelectModule } from '@angular/material/select'; 44 | import { MatSidenavModule } from '@angular/material/sidenav'; 45 | import { MatSliderModule } from '@angular/material/slider'; 46 | import { MatSlideToggleModule } from '@angular/material/slide-toggle'; 47 | import { MatSnackBarModule } from '@angular/material/snack-bar'; 48 | import { MatSortModule } from '@angular/material/sort'; 49 | import { MatTableModule } from '@angular/material/table'; 50 | import { MatTabsModule } from '@angular/material/tabs'; 51 | import { MatToolbarModule } from '@angular/material/toolbar'; 52 | import { MatTooltipModule } from '@angular/material/tooltip'; 53 | import { MatTreeModule } from '@angular/material/tree'; 54 | import { MainComponent } from './main/main.component'; 55 | import { SequenceDiagramDialogComponent } from './sequence-diagram-dialog/sequence-diagram-dialog.component'; 56 | import { TagTableComponent } from './tag-table/tag-table.component'; 57 | import { StickyNoteComponent } from './sticky-note/sticky-note.component'; 58 | import { HistoryComponent } from './history/history.component'; 59 | import { ExpandedDynamicTableComponent } from './expanded-dynamic-table/expanded-dynamic-table.component'; 60 | import { DynamicTableComponent } from './dynamic-table/dynamic-table.component'; 61 | import { TracingProviderService } from './tracing-provider.service'; 62 | import { ServerSideTracingProviderService } from './tracing-provider/server-side-tracing-provider.service'; 63 | import { ZipkinProviderService } from './tracing-provider/zipkin-provider.service'; 64 | 65 | @NgModule({ 66 | declarations: [ 67 | AppComponent, 68 | MenuComponent, 69 | SequenceDiagramComponent, 70 | MainComponent, 71 | SequenceDiagramDialogComponent, 72 | TagTableComponent, 73 | StickyNoteComponent, 74 | HistoryComponent, 75 | ExpandedDynamicTableComponent, 76 | DynamicTableComponent, 77 | ], 78 | exports: [ 79 | A11yModule, 80 | CdkStepperModule, 81 | CdkTableModule, 82 | CdkTreeModule, 83 | DragDropModule, 84 | MatAutocompleteModule, 85 | MatBadgeModule, 86 | MatBottomSheetModule, 87 | MatButtonModule, 88 | MatButtonToggleModule, 89 | MatCardModule, 90 | MatCheckboxModule, 91 | MatChipsModule, 92 | MatStepperModule, 93 | MatDatepickerModule, 94 | MatDialogModule, 95 | MatDividerModule, 96 | MatExpansionModule, 97 | MatGridListModule, 98 | MatIconModule, 99 | MatInputModule, 100 | MatListModule, 101 | MatMenuModule, 102 | MatNativeDateModule, 103 | MatPaginatorModule, 104 | MatProgressBarModule, 105 | MatProgressSpinnerModule, 106 | MatRadioModule, 107 | MatRippleModule, 108 | MatSelectModule, 109 | MatSidenavModule, 110 | MatSliderModule, 111 | MatSlideToggleModule, 112 | MatSnackBarModule, 113 | MatSortModule, 114 | MatTableModule, 115 | MatTabsModule, 116 | MatToolbarModule, 117 | MatTooltipModule, 118 | MatTreeModule, 119 | PortalModule, 120 | ScrollingModule, 121 | ], entryComponents: [SequenceDiagramDialogComponent] 122 | , 123 | imports: [ 124 | BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }), 125 | HttpClientModule, 126 | FormsModule, 127 | A11yModule, 128 | CdkStepperModule, 129 | CdkTableModule, 130 | CdkTreeModule, 131 | DragDropModule, 132 | MatAutocompleteModule, 133 | MatBadgeModule, 134 | MatBottomSheetModule, 135 | MatButtonModule, 136 | MatButtonToggleModule, 137 | MatCardModule, 138 | MatCheckboxModule, 139 | MatChipsModule, 140 | MatStepperModule, 141 | MatDatepickerModule, 142 | MatDialogModule, 143 | MatDividerModule, 144 | MatExpansionModule, 145 | MatGridListModule, 146 | MatIconModule, 147 | MatInputModule, 148 | MatListModule, 149 | MatMenuModule, 150 | MatNativeDateModule, 151 | MatPaginatorModule, 152 | MatProgressBarModule, 153 | MatProgressSpinnerModule, 154 | MatRadioModule, 155 | MatRippleModule, 156 | MatSelectModule, 157 | MatSidenavModule, 158 | MatSliderModule, 159 | MatSlideToggleModule, 160 | MatSnackBarModule, 161 | MatSortModule, 162 | MatTableModule, 163 | MatTabsModule, 164 | MatToolbarModule, 165 | MatTooltipModule, 166 | MatTreeModule, 167 | PortalModule, 168 | ScrollingModule, 169 | BrowserAnimationsModule, 170 | 171 | RouterModule.forRoot([ 172 | { path: '**', component: MainComponent }, 173 | ], { useHash: true }) 174 | ], 175 | providers: [DatePipe, 176 | { provide: 'TracingProviderService', useClass: ZipkinProviderService, multi: true }, 177 | { provide: 'TracingProviderService', useClass: ServerSideTracingProviderService, multi: true }, 178 | ], 179 | bootstrap: [AppComponent] 180 | }) 181 | export class AppModule { } 182 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](https://img.shields.io/badge/STATUS-NOT%20CURRENTLY%20MAINTAINED-red.svg?longCache=true&style=flat) 2 | 3 | # Important Notice 4 | This public repository is read-only and no longer maintained. 5 | 6 | # Tracer 7 | 8 | Tracing visualization and debugging assistant for distributed systems. 9 | 10 | [![alt text](https://github.com/sap-staging/Tracer/blob/master/ReadMe/Main2.PNG)](https://tracer-demo.web.app) 11 | 12 | **Contents** 13 | 14 | - [Tracer](#tracer) 15 | - [Demo](https://tracer-demo.web.app) 16 | - [Why should I use Tracer?](#why-should-i-use-tracer) 17 | - [How to run on your development machine](#how-to-run-on-your-development-machine) 18 | - [Build](#build) 19 | - [Docker](#docker) 20 | - [Docker Build](#docker-build) 21 | - [Docker Run ](#docker-run) 22 | - [Tracing Provider](#tracing-provider) 23 | - [Zipkin Provider](#zipkin-provider) 24 | - [Server Side Tracing Provider](#server-side-tracing-provider) 25 | - [File provider](#file-provider) 26 | - [Data Model](#data-model) 27 | - [Ordering](#ordering) 28 | - [License](#license) 29 | 30 | 31 | 32 | ## Demo 33 | 34 | The [demo](https://tracer-demo.web.app) is configure with few record in the history panel. 35 | 36 | ## Why should I use Tracer? 37 | 38 | For me, it all starts when I diagnosed a production issue that spared across a few microservices. 39 | While trying to make sense I drew the flow who call who, why? 40 | Then realized that I spent a lot of time and effort trying to understand the flow and visual it. 41 | 42 | I decided to find a better way, then when Tracer was born. 43 | 44 | Tracer helps you focus on the "bigger picture" by exposing you to a simplified sequence diagram. 45 | There are other great tools most of them are focus performance or better suite to read flow that compose form hundreds of interaction. 46 | 47 | ## How to run on your development machine 48 | 49 | Run `ng serve` for a dev server. 50 | Navigate to `http://localhost:4200/`. 51 | The app will automatically reload if you change any of the source files. 52 | 53 | ## Build 54 | 55 | Run `ng build` to build the project. 56 | The build artifacts will be stored in the `dist/` directory. 57 | Use the `--prod` flag for a production build. 58 | 59 | ## Docker 60 | 61 | ### Docker Build 62 | 63 | ```bash 64 | docker build -t tracer . 65 | ``` 66 | 67 | ### Docker Run 68 | 69 | ```bash 70 | docker run 71 | --env TRACER_ENV_TracingProviderName='serverSide' 72 | --env TRACER_ENV_TracingProviderUrl='http://Server' 73 | -p 127.0.0.1:3001:80 tracer 74 | ``` 75 | 76 | | environment variables | Values | Description | 77 | | ------------------------------ | --------------------- | --------------------------------- | 78 | | TRACER_ENV_TracingProviderName | `zipkin`,`serverSide` | Setting the Tracing provider name | 79 | | TRACER_ENV_TracingProviderUrl | URL http://Server | Tracing provider server | 80 | 81 | visit the [app]( http://localhost:3001 ) 82 | 83 | 84 | 85 | ## Tracing Provider 86 | 87 | After selecting the tracing provider it should be configure in the environments settings (`\src\environments\environment.prod.ts` and `\src\environments\environment.ts`). 88 | 89 | ### Zipkin Provider 90 | 91 | ```javascript 92 | tracingProvider: { 93 | name: 'zipkin', 94 | url: 'http://localhost:9411' 95 | } 96 | ``` 97 | 98 | > :bulb: it use internally Zipkin v2 API [/trace/{traceId}](https://zipkin.io/zipkin-api/#/default/get_trace__traceId_) 99 | 100 | 101 | ### Server Side Tracing Provider 102 | 103 | ```javascript 104 | tracingProvider: { 105 | name: 'serverSide', 106 | url: 'http://YourSearchService.com/v1' 107 | } 108 | ``` 109 | 110 | Server Side tracing provider let you create your own provider without chancing tracer source code. 111 | 112 | Create a API that receive Get request to ```/trace/{traceId}``` and return [Tracer format](https://github.com/SAP/Tracer#source-format). 113 | 114 | > :bulb: Add [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) support by adding header "Access-Control-Allow-Origin", "*"` . 115 | 116 | ### File provider 117 | 118 | * Load event list from disk. 119 | 120 | * Save events on disk. 121 | 122 | 123 | 124 | ### Data Model 125 | 126 | The application receive from the tracing provider array of Data Model: 127 | 128 | ```javascript 129 | [ 130 | { 131 | 'tracer': { 132 | 'traceId': 'guid', 133 | 'direction': 0, 134 | 'durationMs': 15, 135 | 'spanId': '2', 136 | 'parentSpanId': '1', 137 | 'from': { 138 | 'name': 'Web' 139 | }, 140 | 'to': { 141 | 'name': 'ShoppingCart' 142 | }, 143 | 'timestamp': 1579170558105231, 144 | 'action': 'GetOrder', 145 | }, 146 | additionalfield1:1, 147 | additionalfield2:2 148 | } 149 | ] 150 | 151 | ``` 152 | 153 | | Field | Description | 154 | | ------------ | ------------------------------------------------------------ | 155 | | traceId | Every flow must have an unique identifier (a flow is comprised of all entities interactions of a single logical transaction)
traceId should be identical across all event. | 156 | | spanId | An unique identifier to define a new scope. Any interactions forked from this one, will inherit it as a **parentSpanId** | 157 | | parentSpanId | The parent scope id (the first scope expected to be with no parentSpanId) | 158 | | durationMs | The time elapsed | 159 | | direction | A numeration that effect the sequence diagram:

**Logical transaction:**
All the inner interactions will be in the same **operation block** .
comprise of start and end, when one of them is missing it will auto generate it (The line courser will be with cross **⥇** ).

**Case 0 logical transaction start** (striate line *→* )
**Case 1 logical transaction end** (dashed line *⇠*)


**Action with no continuation:**
A simple line with no side effect ,Log are excellent example of it.
**Case 2 Action Start** ( striate line *→*)
| 160 | | action | The action title, e.g. login, GetUserList | 161 | | timestamp | The timestamp the action started
Epoch **microseconds** of this event. | 162 | | error | An error message, if present, changes the line styling to red. | 163 | | from.name | A system name generates this request | 164 | | to.name | A system, the request calling to (in a log entry it calling to itself) | 165 | | metadata | An auto generated field (don't use this field) | 166 | 167 | Any additional fields will be automatically added and can be examine. 168 | 169 | 170 | 171 | ## Ordering 172 | 173 | The Sequence Diagram is like tree that start at the top item. 174 | The next level are all the node (spanId) with the same preantSpanId . 175 | 176 | Top item: recommended to be one event with no parentSpanId. 177 | 178 | ## License 179 | 180 | Copyright (c) 2019 SAP SE or an SAP affiliate company. All rights reserved. 181 | This file is licensed under the Apache 2.0 except as noted otherwise in the [LICENSE file](https://github.com/sap-staging/Tracer/blob/master/LICENSE). 182 | 183 | Please note that Docker images can contain other software which may be licensed under different licenses. 184 | This License file is also included in the Docker image. 185 | For any usage of built Docker images please make sure to check the licenses of the artifacts contained in the images. 186 | -------------------------------------------------------------------------------- /src/app/sequence-diagram/sequence-diagram.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input } from '@angular/core'; 2 | import * as mermaid from 'mermaid'; 3 | import { EventModel, Direction } from '../model/event-model'; 4 | import { MatDialog } from '@angular/material/dialog'; 5 | import { SequenceDiagramDialogComponent } from '../sequence-diagram-dialog/sequence-diagram-dialog.component'; 6 | import { ScrollingVisibility, ScrollDispatcher, CdkScrollable } from '@angular/cdk/overlay'; 7 | 8 | interface Dictionary { 9 | [Key: string]: T; 10 | } 11 | @Component({ 12 | selector: 'app-sequence-diagram', 13 | templateUrl: './sequence-diagram.component.html', 14 | styleUrls: ['./sequence-diagram.component.css'] 15 | }) 16 | export class SequenceDiagramComponent implements OnInit { 17 | 18 | public _mermaidMarkdown: string[] = []; 19 | public ShouldShow: boolean; 20 | public MermaidData: string; 21 | _orderedEvents: EventModel[]; 22 | @Input() 23 | set orderEvents(orderedEvents: EventModel[]) { 24 | this._mermaidMarkdown = []; 25 | this._orderedEvents = []; 26 | this.ShouldShow = false; 27 | 28 | if (orderedEvents && orderedEvents.length > 0) { 29 | this._orderedEvents = orderedEvents; 30 | this._mermaidMarkdown = this.convertEventsToMermaid(orderedEvents); 31 | this.init(); 32 | } 33 | } 34 | 35 | convertEventsToMermaid(orderedEvents: EventModel[]): any { 36 | const output: string[] = ['sequenceDiagram']; 37 | const names: Dictionary = {}; 38 | const escape = value => 39 | value ? 40 | value 41 | .replace('-', '_') 42 | .replace('+', '') 43 | .replace('>', '') 44 | .replace('<', '') 45 | .replace('+', '') 46 | .replace(';', '') 47 | .replace('\n', ' ') : 'noValue'; 48 | const saveSameCasing = (value: string) => { 49 | 50 | 51 | let item = names[value.toLowerCase()]; 52 | if (item) { 53 | return item; 54 | } else { 55 | item = value; 56 | names[value.toLowerCase()] = item; 57 | return item; 58 | } 59 | }; 60 | const sorter = value => { 61 | if (value.length > 50) { 62 | value = value.substring(0, 50) + '...'; 63 | } 64 | return value; 65 | }; 66 | 67 | orderedEvents.forEach(event => { 68 | const t = event.tracer.durationMs > 1000 ? `${Math.round(event.tracer.durationMs / 1000 * 100) / 100} sec` : Math.round(event.tracer.durationMs) + ' ms'; 69 | const time: string = !event.tracer.metadata.isFake && event.tracer.durationMs && event.tracer.durationMs >= 1 ? ` ⌛ ${t}` : ``; 70 | 71 | let action = event.tracer.action; 72 | 73 | 74 | const from = saveSameCasing(escape(event.tracer.from.nickName || event.tracer.from.name)); 75 | const to = saveSameCasing(escape(event.tracer.to.nickName || event.tracer.to.name)); 76 | action = escape(action); 77 | 78 | let lineType = ''; 79 | if (event.tracer.direction === Direction.ActionStart) { 80 | lineType = '->>'; 81 | } else if (event.tracer.direction === Direction.LogicalTransactionStart && !event.tracer.metadata.isFake) { 82 | lineType = '->>+'; 83 | } else if (event.tracer.direction === Direction.LogicalTransactionStart && event.tracer.metadata.isFake) { 84 | lineType = '-X+'; 85 | } else if (event.tracer.direction === Direction.LogicalTransactionEnd && !event.tracer.metadata.isFake) { 86 | lineType = '-->>-'; 87 | } else if (event.tracer.direction === Direction.LogicalTransactionEnd && event.tracer.metadata.isFake) { 88 | lineType = '--X-'; 89 | } 90 | output.push(`${from}${lineType}${to}:${sorter(action)} ${time}`); 91 | 92 | if (event.tracer.durationMs > 500) { 93 | output.push(`Note over ${from},${to}: Request took ${time}`); 94 | } 95 | }); 96 | 97 | return output; 98 | } 99 | public async init() { 100 | this.ShouldShow = true; 101 | this.MermaidData = this._mermaidMarkdown.join('\n'); 102 | 103 | await new Promise(res => setTimeout(res, 1)); 104 | 105 | mermaid.initialize({ 106 | theme: 'dark', 107 | sequence: { actorMargin: 50, useMaxWidth: false }, 108 | startOnLoad: true 109 | }); 110 | mermaid.init(); 111 | const actorTexts = document.querySelectorAll('.actor'); 112 | for (let i = 0; i < actorTexts.length; i++) { 113 | 114 | const element = actorTexts[i]; 115 | if (element.nodeName === 'text') { 116 | const text1 = element.children[0]; 117 | text1.textContent = element.children[0].textContent.replace('_0_', ' '); 118 | 119 | } 120 | 121 | this.FixedHeader(0); 122 | } 123 | const texts = document.querySelectorAll('.messageText'); 124 | 125 | for (let i = 0; i < texts.length; i++) { 126 | const item = this._orderedEvents[i]; 127 | const text = texts[i]; 128 | 129 | (text as any).onclick = () => { 130 | this.dialog.open(SequenceDiagramDialogComponent, { data: item }); 131 | }; 132 | const line = text.parentNode.children[1] as any; 133 | (line).onclick = () => { 134 | this.dialog.open(SequenceDiagramDialogComponent, { data: item }); 135 | }; 136 | if ((item.tracer.error)) { 137 | line.style.strokeWidth = 2; 138 | line.style.stroke = 'red'; 139 | 140 | } 141 | } 142 | } 143 | 144 | public FixedHeader(scrollTop) { 145 | const hideScroll = document.getElementById('hideScroll'); 146 | const actors = Array.from(document.getElementsByClassName('actor')); 147 | const scrollTopRect: number = scrollTop + 0; 148 | const scrollTopText: number = scrollTop + 32; 149 | if (!hideScroll && actors && actors.length > 0) { 150 | // 151 | const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect'); 152 | const firstBottomActor = actors.length / 4 * 2; 153 | actors[firstBottomActor].parentElement.insertBefore(rect, actors[firstBottomActor]); 154 | const height = (Number.parseInt(actors[0].getAttribute('height')) + 30).toString(); 155 | const svgSize = actors[firstBottomActor].parentElement.parentElement.getAttribute('width'); 156 | 157 | rect.setAttribute('x', '0'); 158 | rect.setAttribute('y', scrollTopRect.toString()); 159 | rect.setAttribute('fill', '#303030'); 160 | rect.setAttribute('width', svgSize); 161 | rect.setAttribute('height', height); 162 | rect.setAttribute('id', 'hideScroll'); 163 | } 164 | if (hideScroll) { 165 | hideScroll.setAttribute('y', (scrollTopRect - 20).toString()); 166 | } 167 | 168 | actors.forEach(x => { 169 | if (x.nodeName === 'rect') { 170 | x.setAttribute('y', scrollTopRect.toString()); 171 | } else { 172 | x.setAttribute('y', scrollTopText.toString()); 173 | } 174 | }); 175 | } 176 | 177 | constructor(private dialog: MatDialog, public scroll: ScrollDispatcher) { } 178 | 179 | ngOnInit() { 180 | 181 | mermaid.initialize({ 182 | theme: 'dark', 183 | sequence: { actorMargin: 50, useMaxWidth: false , width:100,heigh:100}, 184 | startOnLoad: false 185 | }); 186 | const tabGroup = document.getElementById('tabGroup'); 187 | 188 | 189 | this.scroll 190 | .scrolled(4) 191 | .subscribe((data: CdkScrollable) => { 192 | const start = tabGroup.offsetTop + 50; 193 | const scrollTop = data.getElementRef().nativeElement.scrollTop; 194 | if (start > scrollTop) { 195 | this.FixedHeader(0); 196 | } else { 197 | this.FixedHeader(scrollTop - start); 198 | } 199 | }); 200 | } 201 | } 202 | 203 | export class Result { 204 | public mermaidMarkdown: string[]; 205 | public events: EventModel[]; 206 | public ordererFlow: EventModel[]; 207 | } 208 | -------------------------------------------------------------------------------- /src/app/main/main.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewChild } from '@angular/core'; 2 | import { EventModel } from '../model/event-model'; 3 | import { FileEvent } from '../menu/menu.component'; 4 | import { OrderManagerService } from '../order-manager.service'; 5 | import { historyRecord } from '../history/history.component'; 6 | import { TracingProviderService } from '../tracing-provider.service'; 7 | import { appSettings } from '../app-settings/app-settings.service'; 8 | import { MatDrawer } from '@angular/material/sidenav'; 9 | import { MatTabChangeEvent } from '@angular/material/tabs'; 10 | import { environment } from 'src/environments/environment'; 11 | 12 | @Component({ 13 | selector: 'app-main', 14 | templateUrl: './main.component.html', 15 | styleUrls: ['./main.component.css'] 16 | }) 17 | export class MainComponent implements OnInit { 18 | public traceId: string; 19 | public selectedTraceId: string; 20 | public error: string; 21 | public note: string; 22 | public loading: boolean; 23 | public searchType = '1'; 24 | public orderEvents: EventModel[] = []; // Use to create a graph at the moment only sequence diagram is supported 25 | public events: EventModel[] = []; // Use to display formatted event 26 | public rawEvents: withMetaData; // Use for save state 27 | public formattedRawEvent: any[]; // Use to display raw data 28 | public SelectedTabIndex: number; 29 | public ShowAggregateSearch: boolean; 30 | public isMobile: boolean; 31 | public isHistoryOpen: boolean; 32 | @ViewChild('HistoryWindow', { static: true }) HistoryWindow: MatDrawer; 33 | @ViewChild('HistoryExpender', { static: true }) HistoryExpender: MatDrawer; 34 | 35 | constructor( 36 | private tracingProviderService: TracingProviderService, 37 | private orderManagerService: OrderManagerService, 38 | private settings: appSettings 39 | ) { } 40 | 41 | ngOnInit(): void { 42 | this.isMobile = window.innerWidth < 800; 43 | this.SetControlInDefaultState(); 44 | const traceId = this.settings.GetTraceId(); 45 | if (traceId) { 46 | { 47 | this.selectedTraceId = traceId; 48 | this.loadFromHistory(traceId); 49 | } 50 | } 51 | } 52 | 53 | private SetControlInDefaultState() { 54 | 55 | if (this.isMobile === false) { 56 | if (this.settings.GetHistoryOpenDefaultPosition()) { 57 | this.HistoryWindow.open(); 58 | } else { 59 | this.HistoryExpender.open(); 60 | } 61 | } 62 | this.isHistoryOpen = this.settings.GetHistoryOpenDefaultPosition(); 63 | this.SelectedTabIndex = this.settings.GetSelectedTabIndex(); 64 | this.ShowAggregateSearch = environment['ShowAggregateSearch']; 65 | } 66 | private loadFromHistory(traceId: string) { 67 | const index = this.settings.GetHistoryRecords().findIndex(x => x.traceId === traceId); 68 | this.traceId = traceId; 69 | this.selectedTraceId = traceId; 70 | 71 | if (index >= 0) { // exists in history 72 | this.note = 'Note: history record, press search to reload the data'; 73 | 74 | this.init(c => Promise.resolve(this.settings.GetHistoryRecords()[index].result)); 75 | } else { 76 | 77 | if (!this.tracingProviderService.HasTracingProvider()) { 78 | this.error = this.tracingProviderService.GetErrorMessage(); 79 | this.loading = false; 80 | return; 81 | } 82 | this.init(c => this.tracingProviderService.GetFlow(c)); 83 | } 84 | } 85 | 86 | private async init(getFlow: (traceId: string) => Promise) { 87 | 88 | this.loading = true; 89 | const currentOperation = this.traceId; 90 | const currentSearchType = this.searchType; 91 | try { 92 | const rawEvent = await getFlow(currentOperation); 93 | if (rawEvent == null || rawEvent.length === 0) { 94 | if (currentOperation === this.traceId && currentSearchType === this.searchType) { 95 | // Optimistic locking 96 | this.error = 'No result found'; 97 | this.loading = false; 98 | return; 99 | } 100 | } 101 | 102 | // Optimistic locking 103 | if (currentOperation === this.traceId && currentSearchType === this.searchType) { 104 | 105 | const remoteCall = this.orderManagerService.CreateMetaDataAndLookUp(rawEvent); 106 | const orderByHierarchy = this.orderManagerService.BuildHierarchy(remoteCall); 107 | let maxDate: Date; 108 | let minDate: Date; 109 | this.traceId = currentOperation; 110 | if (rawEvent && rawEvent.length > 0) { 111 | const times = rawEvent.map(x => x.tracer.timestamp > 0 ? x.tracer.timestamp / 1000 : 1); 112 | maxDate = new Date(Math.max.apply(Math, times)); 113 | minDate = new Date(Math.min.apply(Math, times)); 114 | } 115 | 116 | this.rawEvents = { traceId: this.traceId, value: rawEvent, startTime: minDate, endTime: maxDate }; 117 | this.events = rawEvent; 118 | this.formattedRawEvent = rawEvent; 119 | this.orderEvents = orderByHierarchy; 120 | 121 | } 122 | } catch (error) { 123 | if (currentOperation === this.traceId && currentSearchType === this.searchType) { 124 | // Optimistic locking 125 | this.handleError(error); 126 | } 127 | } 128 | this.loading = false; 129 | } 130 | 131 | private handleError(error: Error) { 132 | this.error = `An error occurred: ${error.message} ${error.stack}`; 133 | } 134 | 135 | // Should disable the search for the same call Id and search type when it running 136 | public Search() { 137 | this.ClearState(); 138 | 139 | this.loading = true; 140 | if (!this.selectedTraceId) { 141 | this.error = 'TraceId not define'; 142 | this.loading = false; 143 | return; 144 | } 145 | 146 | if (!this.tracingProviderService.HasTracingProvider()) { 147 | this.error = this.tracingProviderService.GetErrorMessage(); 148 | this.loading = false; 149 | return; 150 | } 151 | 152 | this.selectedTraceId = this.selectedTraceId.trim(); 153 | this.traceId = this.selectedTraceId; 154 | this.settings.SetTraceId(this.traceId); 155 | 156 | this.traceId = this.selectedTraceId; 157 | this.init(c => this.tracingProviderService.GetFlow(c)); 158 | 159 | } 160 | 161 | public ClearState() { 162 | this.rawEvents = null; 163 | this.orderEvents = []; 164 | this.events = []; 165 | this.error = ''; 166 | this.note = ''; 167 | this.formattedRawEvent = []; 168 | this.events = []; 169 | } 170 | 171 | loadFormHistory(event: historyRecord) { 172 | this.settings.SetTraceId(event.traceId); 173 | 174 | this.selectedTraceId = event.traceId; 175 | 176 | this.ClearState(); 177 | new Promise(res => setTimeout(res, 1)).then(x => { 178 | // it in promise due to a bug in memrid 179 | this.traceId = event.traceId; 180 | 181 | this.init(c => Promise.resolve(event.result)); 182 | }); 183 | } 184 | 185 | openFileTrigger(fileEvent: FileEvent) { 186 | if (!fileEvent.content || !fileEvent.name) { 187 | this.error = 'file content is undefined'; 188 | return; 189 | } 190 | 191 | this.ClearState(); 192 | 193 | try { 194 | const rawEvent: EventModel[] = JSON.parse(fileEvent.content); 195 | if (rawEvent && rawEvent.length > 0 && rawEvent[0].tracer.traceId) { 196 | this.traceId = rawEvent[0].tracer.traceId; 197 | this.settings.SetTraceId(rawEvent[0].tracer.traceId); 198 | 199 | this.selectedTraceId = rawEvent[0].tracer.traceId; 200 | 201 | new Promise(res => setTimeout(res, 1)).then(x => { 202 | // it in promise due to a bug in memrid 203 | this.init(c => Promise.resolve(rawEvent)); 204 | }); 205 | } 206 | } catch (error) { 207 | this.error = error; 208 | } 209 | } 210 | 211 | public tabChanged(tabChangeEvent: MatTabChangeEvent): void { 212 | this.settings.SetSelectedTabIndex(tabChangeEvent.index); 213 | 214 | } 215 | 216 | public ChangeHistoryOpenState() { 217 | if (this.isMobile === false) { 218 | this.HistoryExpender.toggle(); 219 | this.HistoryWindow.toggle(); 220 | } 221 | this.settings.SetHistoryOpenDefaultPosition(!this.settings.GetHistoryOpenDefaultPosition()); 222 | this.isHistoryOpen = this.settings.GetHistoryOpenDefaultPosition(); 223 | 224 | this.settings.save(); 225 | } 226 | 227 | public showAggregateSearchChange() { 228 | this.settings.SetSearchType(this.searchType); 229 | } 230 | } 231 | 232 | // tslint:disable-next-line: class-name 233 | export class withMetaData { 234 | value: T; 235 | traceId: string; 236 | startTime: Date; 237 | endTime: Date; 238 | } 239 | 240 | -------------------------------------------------------------------------------- /src/app/order-manager.service.ts: -------------------------------------------------------------------------------- 1 | import { EventModel, Direction, Metadata, Server, Tracer } from './model/event-model'; 2 | import { Injectable } from '@angular/core'; 3 | import { E } from '@angular/cdk/keycodes'; 4 | interface Dictionary { 5 | [Key: string]: T; 6 | } 7 | 8 | 9 | const getRequestDestination = (event: EventModel) => { 10 | if (event.tracer.spanId) { 11 | return `${event.tracer.direction}_${event.tracer.spanId}_${event.tracer.metadata.count}`; 12 | } else { // To support broken event 13 | return `${event.tracer.from.name && event.tracer.from.name.toLowerCase()}->${event.tracer.to && event.tracer.to.name && event.tracer.to.name.toLowerCase() 14 | }:.${event.tracer.direction}_${event.tracer.spanId}_${event.tracer.metadata.count}`; 15 | } 16 | }; 17 | 18 | const getRequestOppositeDestination = (event: EventModel) => { 19 | const direction = event.tracer.direction === Direction.LogicalTransactionStart ? 20 | Direction.LogicalTransactionEnd : Direction.LogicalTransactionStart; 21 | 22 | if (event.tracer.spanId) { 23 | return `${direction}_${event.tracer.spanId}_${event.tracer.metadata.count}`; 24 | } else { // To support broken event 25 | return `${event.tracer.to && event.tracer.to.name && event.tracer.to.name.toLowerCase() 26 | }->${event.tracer.from.name && event.tracer.from.name.toLowerCase()}:.${direction}_${event.tracer.spanId}_${event.tracer.metadata.count}`; 27 | } 28 | }; 29 | 30 | @Injectable({ 31 | providedIn: 'root' 32 | }) 33 | 34 | 35 | export class OrderManagerService { 36 | constructor() { } 37 | 38 | private DeleteUserMetadata(events: EventModel[]) { 39 | // ignore the user metadata 40 | events.forEach(element => { 41 | delete element.tracer['metadata']; 42 | delete element.tracer.to.nickName; 43 | delete element.tracer.from.nickName; 44 | element.tracer.metadata = {} as Metadata; 45 | 46 | }); 47 | } 48 | 49 | 50 | private CreateLookUp(events: EventModel[]): Dictionary { 51 | const dictionary: Dictionary = {}; 52 | let count = 1; 53 | 54 | events = events.sort((a, b) => a.tracer.timestamp - b.tracer.timestamp); 55 | 56 | events.forEach(event => { 57 | let id: string = getRequestDestination(event); 58 | 59 | // support duplicate event 60 | if (dictionary[id]) { 61 | event.tracer.metadata.count = count++; 62 | id = getRequestDestination(event); 63 | } 64 | 65 | dictionary[id] = event; 66 | }); 67 | 68 | return dictionary; 69 | } 70 | 71 | 72 | 73 | private CreateFakeEvent(oppositeEvent: EventModel): EventModel { 74 | return { 75 | tracer: { 76 | direction: oppositeEvent.tracer.direction === Direction.LogicalTransactionStart ? Direction.LogicalTransactionEnd : Direction.LogicalTransactionStart, 77 | spanId: oppositeEvent.tracer.spanId, 78 | parentSpanId: oppositeEvent.tracer.parentSpanId, 79 | from: oppositeEvent.tracer.to, 80 | to: oppositeEvent.tracer.from, 81 | action: oppositeEvent.tracer.action, 82 | timestamp: oppositeEvent.tracer.timestamp, 83 | metadata: { 84 | count: oppositeEvent.tracer.metadata.count, 85 | isFake: true, 86 | } 87 | } as Tracer 88 | } as EventModel; 89 | } 90 | 91 | 92 | private CreateOppositeEvent(dictionary: Dictionary, events: EventModel[]) { 93 | events.filter(x => x.tracer.direction === Direction.LogicalTransactionStart || x.tracer.direction === Direction.LogicalTransactionEnd) 94 | .forEach(event => { 95 | 96 | const oppositeEventId: string = getRequestOppositeDestination(event); 97 | const oppositeEvent: EventModel = dictionary[oppositeEventId]; 98 | 99 | if (!oppositeEvent) { 100 | dictionary[oppositeEventId] = this.CreateFakeEvent(event); 101 | } 102 | }); 103 | } 104 | 105 | CreateMetaDataAndLookUp(events: EventModel[]): Dictionary { 106 | this.DeleteUserMetadata(events); 107 | const dictionary = this.CreateLookUp(events); 108 | this.CreateOppositeEvent(dictionary, events); 109 | return dictionary; 110 | } 111 | 112 | BuildHierarchy(events: Dictionary): EventModel[] { 113 | const ServerNameToNode: ServerNameToEvents = new ServerNameToEvents(); 114 | 115 | const rootsCandidate: EventModel[] = Object.keys(events).map(x => events[x]) 116 | .filter(event => event.tracer.direction === Direction.LogicalTransactionStart 117 | || event.tracer.direction === Direction.ActionStart); 118 | 119 | // add all parent flows to ordered flow so we can initiate the processing 120 | 121 | let output: EventModel[] = []; 122 | let hasMissingFlows: Boolean = true; 123 | const visitRoots: EventModel[] = []; 124 | // More Root can be added later if not connected to the root span chain 125 | const roots = rootsCandidate.filter(event => !event.tracer.parentSpanId) 126 | .sort((a, b) => b.tracer.timestamp - a.tracer.timestamp); 127 | 128 | while (hasMissingFlows) { 129 | if (roots.length !== 0) { 130 | const root = roots.pop(); 131 | visitRoots.push(root); 132 | const result = this.BuildRootHierarchy(root, events, rootsCandidate, ServerNameToNode); 133 | output = output.concat(result); 134 | } else { 135 | // when your log is events that not order by Hierarchy we need to do best effort to extract them 136 | const missingEvent = rootsCandidate. 137 | filter(event => !event.tracer.metadata.visit && visitRoots.findIndex(x => x === event) === -1) 138 | .sort((a, b) => a.tracer.timestamp - b.tracer.timestamp); 139 | if (missingEvent && missingEvent.length > 0) { 140 | roots.push(missingEvent[0]); 141 | } else { 142 | hasMissingFlows = false; 143 | } 144 | } 145 | } 146 | ServerNameToNode.EnrichMetadataServerName(); 147 | return output; 148 | } 149 | 150 | // sort the events according to flow hierarchy 151 | // It also build another hierarchy that map each event to server to create single nickname for server 152 | private BuildRootHierarchy(root: EventModel, events: Dictionary 153 | , requestToOtherSystems: EventModel[], serverToNodes: ServerNameToEvents) { 154 | const output: EventModel[] = []; 155 | const orderedFlows: EventModel[] = []; 156 | orderedFlows.push(root); 157 | while (orderedFlows.length > 0) { 158 | const flow: EventModel = orderedFlows.pop(); 159 | 160 | if (flow.tracer.metadata.visit) { 161 | continue; 162 | } 163 | 164 | output.push(flow); 165 | flow.tracer.metadata.visit = true; 166 | serverToNodes.AddNode(flow); 167 | // check if this flow has a closing event 168 | const direction = flow.tracer.direction; 169 | 170 | // try to add close event in the end 171 | if (direction === Direction.LogicalTransactionStart) { 172 | const closeEventId: string = getRequestOppositeDestination(flow); 173 | const closeEvent = events[closeEventId]; 174 | if (closeEvent) { 175 | orderedFlows.push(closeEvent); 176 | } 177 | } 178 | 179 | // find child flow to put in the middle 180 | if (direction === Direction.LogicalTransactionStart || direction === Direction.ActionStart) { 181 | 182 | const parentSpan = flow.tracer.spanId; 183 | if (parentSpan) { 184 | const childFlows = requestToOtherSystems 185 | .filter(event => event.tracer.parentSpanId === parentSpan); 186 | 187 | if (childFlows) { 188 | // order is opposite of stack 189 | childFlows.sort((a, b) => b.tracer.timestamp - a.tracer.timestamp).forEach((child: EventModel) => { 190 | orderedFlows.push(child); 191 | }); 192 | } 193 | } 194 | } 195 | 196 | } 197 | 198 | // missing all event the not seen 199 | return output; 200 | } 201 | 202 | } 203 | 204 | class ServerNameToEvents { 205 | Lookup: Dictionary = {}; 206 | public AddNode(event: EventModel) { 207 | // if (!event.tracer.from) { event.tracer.from = {} as Server } 208 | // if (!event.tracer.from) { event.tracer.to = {} as Server} 209 | 210 | this.insertToLookup(event, event.tracer.spanId, false); 211 | this.insertToLookup(event, event.tracer.parentSpanId, true); 212 | 213 | 214 | } 215 | insertToLookup(event: EventModel, span: string, isParent: boolean) { 216 | if (!span) { 217 | return; 218 | } 219 | 220 | const A = this.Lookup[span + 'F'] || [] as Server[]; 221 | const B = this.Lookup[span + 'T'] || [] as Server[]; 222 | 223 | this.Lookup[span + 'F'] = A; 224 | this.Lookup[span + 'T'] = B; 225 | 226 | if (!isParent) { 227 | // Get dic of opposite if has no parent search other direction ... 228 | if (event.tracer.direction === Direction.ActionStart || event.tracer.direction === Direction.LogicalTransactionStart) { 229 | A.push(event.tracer.from); 230 | B.push(event.tracer.to); 231 | } else { 232 | A.push(event.tracer.to); 233 | B.push(event.tracer.from); 234 | } 235 | } else { 236 | if (event.tracer.direction === Direction.ActionStart || event.tracer.direction === Direction.LogicalTransactionStart) { 237 | B.push(event.tracer.from); 238 | } else { 239 | B.push(event.tracer.to); 240 | } 241 | 242 | } 243 | } 244 | 245 | public EnrichMetadataServerName() { 246 | const nameToNickNames: Dictionary = {}; 247 | Object.keys(this.Lookup).forEach(server => { 248 | 249 | let nickName: string; 250 | this.Lookup[server].forEach(event => { 251 | if (!nickName && event && event.name) { 252 | nickName = event.name; 253 | } 254 | if (event && event.name && nameToNickNames[event.name]) { 255 | nickName = nameToNickNames[event.name]; 256 | } 257 | }); 258 | 259 | if (!nickName || nickName === 'unknown' || nickName === null) { return; } 260 | 261 | 262 | this.Lookup[server].forEach(event => { 263 | event.nickName = nickName; 264 | 265 | // set all nicknames 266 | if (event && event.name && event.name !== null && event.name !== 'unknown') { 267 | nameToNickNames[event.name] = nickName; 268 | } 269 | }); 270 | }); 271 | } 272 | 273 | } 274 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /src/app/app-settings/history-example.ts: -------------------------------------------------------------------------------- 1 | 2 | export class HistoryExample { 3 | 4 | historyRecord: any[] = [{ 5 | traceId: 'OrderFlow-Guid', 6 | result: [ 7 | { 8 | tracer: { 9 | 'traceId': 'OrderFlow-Guid', 10 | 'direction': 1, 11 | 'durationMs': 15, 12 | 'spanId': '1', 13 | 'from': { 14 | 'name': 'Web' 15 | }, 16 | 'to': { 17 | 'name': 'User' 18 | }, 19 | 'timestamp': 1502787600000000, 20 | 'action': 'Order', 21 | } 22 | , 'userId': 1343 23 | 24 | }, 25 | { 26 | tracer: 27 | { 28 | 'traceId': 'OrderFlow-Guid', 29 | 'direction': 0, 30 | 'durationMs': 15, 31 | 'spanId': '2', 32 | 'parentSpanId': '1', 33 | 'from': { 34 | 'name': 'Web' 35 | }, 36 | 'to': { 37 | 'name': 'ShoppingCart' 38 | }, 39 | 'timestamp': 1502787600000000, 40 | 'action': 'GetOrder', 41 | } 42 | , 'userId': 1343 43 | 44 | }, 45 | { 46 | tracer: { 47 | 'traceId': 'OrderFlow-Guid', 48 | 'direction': 1, 49 | 'durationMs': 15, 50 | 'spanId': '2', 51 | 'parentSpanId': '1', 52 | 'from': { 53 | 'name': 'ShoppingCart' 54 | }, 55 | 'to': { 56 | 'name': 'Web' 57 | }, 58 | 'timestamp': 1502787600000000, 59 | 'action': 'GetOrder', 60 | } 61 | , 'userId': 1343 62 | 63 | }, 64 | { 65 | tracer: { 66 | 'traceId': 'OrderFlow-Guid', 67 | 'direction': 0, 68 | 'durationMs': 15, 69 | 'spanId': '3', 70 | 'parentSpanId': '1', 71 | 'from': { 72 | 'name': 'Web' 73 | }, 74 | 'to': { 75 | 'name': 'OrderService' 76 | }, 77 | 'timestamp': 1502787600000000, 78 | 'action': 'ConfirmOrder', 79 | } 80 | , 'userId': 1343 81 | 82 | } 83 | , { 84 | tracer: { 85 | 'traceId': 'OrderFlow-Guid', 86 | 'direction': 0, 87 | 'durationMs': 15, 88 | 'spanId': '4', 89 | 'parentSpanId': '3', 90 | 'from': { 91 | 'name': 'OrderService' 92 | }, 93 | 'to': { 94 | 'name': 'DB' 95 | }, 96 | 'timestamp': 1502787600000000, 97 | 'action': 'Query', 98 | } 99 | , 'userId': 1343 100 | 101 | }, 102 | { 103 | tracer: { 104 | 'traceId': 'OrderFlow-Guid', 105 | 'direction': 3, 106 | 'durationMs': 0, 107 | 'parentSpanId': '1', 108 | 'from': { 109 | 'name': 'Web' 110 | }, 111 | 'to': { 112 | 'name': 'Web' 113 | }, 114 | 'timestamp': 1502787600000000, 115 | 'action': 'FindSession', 116 | } 117 | , 'userId': 1343 118 | 119 | } 120 | ] 121 | }, 122 | { 123 | traceId: 'FailedOrderFlow-Guid', 124 | result: [ 125 | { 126 | tracer: { 127 | 'traceId': 'FailedOrderFlow-Guid', 128 | 'direction': 1, 129 | 'spanId': '1', 130 | 'from': { 131 | 'name': 'Web' 132 | }, 133 | 'to': { 134 | 'name': 'User' 135 | }, 136 | 'timestamp': 1502787600000000, 137 | 'action': 'Order', 138 | 'durationMs': 10000, 139 | 'error': 'TimeOut' 140 | }, 141 | 'UserId': 1343 142 | 143 | }, 144 | { 145 | tracer: { 146 | 'traceId': 'FailedOrderFlow-Guid', 147 | 'direction': 0, 148 | 'spanId': '2', 149 | 'parentSpanId': '1', 150 | 'from': { 151 | 'name': 'Web' 152 | }, 153 | 'to': { 154 | 'name': 'ShoppingCart' 155 | }, 156 | 'timestamp': 1502787600000000, 157 | 'action': 'GetOrder', 158 | 'durationMs': 10000, 159 | 'error': 'TimeOut' 160 | } 161 | , 'userId': 1343 162 | }, 163 | { 164 | tracer: { 165 | 'traceId': 'FailedOrderFlow-Guid', 166 | 'direction': 1, 167 | 'spanId': '2', 168 | 'parentSpanId': '1', 169 | 'from': { 170 | 'name': 'ShoppingCart' 171 | }, 172 | 'to': { 173 | 'name': 'Web' 174 | }, 175 | 'timestamp': 1502787600000000, 176 | 'action': 'GetOrder', 177 | 'durationMs': 10000, 178 | 'error': 'TimeOut' 179 | 180 | } 181 | , 'userId': 1343 182 | 183 | }, 184 | { 185 | tracer: { 186 | 'traceId': 'FailedOrderFlow-Guid', 187 | 'direction': 0, 188 | 'durationMs': 15, 189 | 'spanId': '3', 190 | 'parentSpanId': '1', 191 | 'from': { 192 | 'name': 'Web' 193 | }, 194 | 'to': { 195 | 'name': 'OrderService' 196 | }, 197 | 'timestamp': 1502787600000000, 198 | 'action': 'ConfirmOrder', 199 | } 200 | , 'userId': 1343 201 | 202 | } 203 | , { 204 | tracer: { 205 | 'traceId': 'FailedOrderFlow-Guid', 206 | 'direction': 0, 207 | 'durationMs': 15, 208 | 'spanId': '4', 209 | 'parentSpanId': '3', 210 | 'from': { 211 | 'name': 'OrderService' 212 | }, 213 | 'to': { 214 | 'name': 'DB' 215 | }, 216 | 'timestamp': 1502787600000000, 217 | 'action': 'Query', 218 | } 219 | , 'userId': 1343 220 | 221 | } 222 | ] 223 | }, 224 | { 225 | traceId: '5aab74dbb904746bb33447baae403ed6', 226 | name:'messaging.json', 227 | result: [ 228 | { 229 | 'tracer': { 230 | 'from': { 231 | 'name': 'frontend' 232 | }, 233 | 'to': {}, 234 | 'action': 'get /', 235 | 'traceId': '5aab74dbb904746bb33447baae403ed6', 236 | 'durationMs': 2.839, 237 | 'spanId': 'b33447baae403ed6', 238 | 'timestamp': 1521186011926119, 239 | 'direction': 1 240 | }, 241 | 'traceId': '5aab74dbb904746bb33447baae403ed6', 242 | 'id': 'b33447baae403ed6', 243 | 'kind': 'SERVER', 244 | 'name': 'get /', 245 | 'timestamp': 1521186011926119, 246 | 'duration': 2839, 247 | 'localEndpoint': { 248 | 'serviceName': 'frontend', 249 | 'ipv4': '192.168.0.10' 250 | }, 251 | 'remoteEndpoint': { 252 | 'ipv6': '::1', 253 | 'port': 54602 254 | }, 255 | 'tags': { 256 | 'http.method': 'GET', 257 | 'http.path': '/', 258 | 'mvc.controller.class': 'Frontend', 259 | 'mvc.controller.method': 'callBackend' 260 | } 261 | }, 262 | { 263 | 'tracer': { 264 | 'from': { 265 | 'name': 'frontend' 266 | }, 267 | 'to': { 268 | 'name': 'rabbitmq' 269 | }, 270 | 'action': 'publish', 271 | 'traceId': '5aab74dbb904746bb33447baae403ed6', 272 | 'durationMs': 0.013, 273 | 'parentSpanId': 'b33447baae403ed6', 274 | 'spanId': '05e3ac9a4f6e3b90', 275 | 'timestamp': 1521186011927475, 276 | 'direction': 2 277 | }, 278 | 'traceId': '5aab74dbb904746bb33447baae403ed6', 279 | 'parentId': 'b33447baae403ed6', 280 | 'id': '05e3ac9a4f6e3b90', 281 | 'kind': 'PRODUCER', 282 | 'name': 'publish', 283 | 'timestamp': 1521186011927475, 284 | 'duration': 13, 285 | 'localEndpoint': { 286 | 'serviceName': 'frontend', 287 | 'ipv4': '192.168.0.10' 288 | }, 289 | 'remoteEndpoint': { 290 | 'serviceName': 'rabbitmq' 291 | } 292 | }, 293 | { 294 | 'tracer': { 295 | 'from': { 296 | 'name': 'backend' 297 | }, 298 | 'to': { 299 | 'name': 'rabbitmq' 300 | }, 301 | 'action': 'next-message', 302 | 'traceId': '5aab74dbb904746bb33447baae403ed6', 303 | 'durationMs': 0.014, 304 | 'parentSpanId': '05e3ac9a4f6e3b90', 305 | 'spanId': 'e457b5a2e4d86bd1', 306 | 'timestamp': 1521186011929043, 307 | 'direction': 3 308 | }, 309 | 'traceId': '5aab74dbb904746bb33447baae403ed6', 310 | 'parentId': '05e3ac9a4f6e3b90', 311 | 'id': 'e457b5a2e4d86bd1', 312 | 'kind': 'CONSUMER', 313 | 'name': 'next-message', 314 | 'timestamp': 1521186011929043, 315 | 'duration': 14, 316 | 'localEndpoint': { 317 | 'serviceName': 'backend', 318 | 'ipv4': '192.168.0.10' 319 | }, 320 | 'remoteEndpoint': { 321 | 'serviceName': 'rabbitmq' 322 | }, 323 | 'tags': { 324 | 'rabbit.exchange': '', 325 | 'rabbit.queue': 'backend', 326 | 'rabbit.routing_key': 'backend' 327 | } 328 | }, 329 | { 330 | 'tracer': { 331 | 'from': { 332 | 'name': 'backend' 333 | }, 334 | 'to': { 335 | 'name': 'backend' 336 | }, 337 | 'action': 'on-message', 338 | 'traceId': '5aab74dbb904746bb33447baae403ed6', 339 | 'durationMs': 0.371, 340 | 'parentSpanId': 'e457b5a2e4d86bd1', 341 | 'spanId': '4ad2db84ac76def7', 342 | 'timestamp': 1521186011929105, 343 | 'direction': 2 344 | }, 345 | 'traceId': '5aab74dbb904746bb33447baae403ed6', 346 | 'parentId': 'e457b5a2e4d86bd1', 347 | 'id': '4ad2db84ac76def7', 348 | 'name': 'on-message', 349 | 'timestamp': 1521186011929105, 350 | 'duration': 371, 351 | 'localEndpoint': { 352 | 'serviceName': 'backend', 353 | 'ipv4': '192.168.0.10' 354 | }, 355 | 'isLog': true 356 | } 357 | ] 358 | 359 | } 360 | , 361 | { 362 | traceId: '0562809467078eab', 363 | name:'messaging-kafka.json', 364 | result: [ 365 | { 366 | 'tracer': { 367 | 'from': { 368 | 'name': 'servicea' 369 | }, 370 | 'to': { 371 | 'name': 'servicea' 372 | }, 373 | 'action': 'on-message', 374 | 'traceId': '0562809467078eab', 375 | 'durationMs': 252.09, 376 | 'parentSpanId': '0562809467078eab', 377 | 'spanId': '09fdf808ef0d60c0', 378 | 'timestamp': 1541405397200002, 379 | 'direction': 2 380 | }, 381 | 'traceId': '0562809467078eab', 382 | 'parentId': '0562809467078eab', 383 | 'id': '09fdf808ef0d60c0', 384 | 'name': 'on-message', 385 | 'timestamp': 1541405397200002, 386 | 'duration': 252090, 387 | 'localEndpoint': { 388 | 'serviceName': 'servicea' 389 | }, 390 | 'isLog': true 391 | }, 392 | { 393 | 'tracer': { 394 | 'from': { 395 | 'name': 'servicea' 396 | }, 397 | 'to': { 398 | 'name': 'kafka' 399 | }, 400 | 'action': 'poll', 401 | 'traceId': '0562809467078eab', 402 | 'durationMs': 0.026, 403 | 'spanId': '0562809467078eab', 404 | 'timestamp': 1541405397200023, 405 | 'direction': 3 406 | }, 407 | 'traceId': '0562809467078eab', 408 | 'id': '0562809467078eab', 409 | 'kind': 'CONSUMER', 410 | 'name': 'poll', 411 | 'timestamp': 1541405397200023, 412 | 'duration': 26, 413 | 'localEndpoint': { 414 | 'serviceName': 'servicea' 415 | }, 416 | 'remoteEndpoint': { 417 | 'serviceName': 'kafka' 418 | }, 419 | 'tags': { 420 | 'kafka.topic': 'messages' 421 | } 422 | }, 423 | { 424 | 'tracer': { 425 | 'from': { 426 | 'name': 'servicea' 427 | }, 428 | 'to': { 429 | 'name': 'servicea' 430 | }, 431 | 'action': 'handle', 432 | 'traceId': '0562809467078eab', 433 | 'durationMs': 0.866, 434 | 'parentSpanId': '09fdf808ef0d60c0', 435 | 'spanId': '5141af930f03274e', 436 | 'timestamp': 1541405397413496, 437 | 'direction': 2 438 | }, 439 | 'traceId': '0562809467078eab', 440 | 'parentId': '09fdf808ef0d60c0', 441 | 'id': '5141af930f03274e', 442 | 'name': 'handle', 443 | 'timestamp': 1541405397413496, 444 | 'duration': 866, 445 | 'localEndpoint': { 446 | 'serviceName': 'servicea' 447 | }, 448 | 'tags': { 449 | 'class': 'Projector', 450 | 'method': 'handle' 451 | }, 452 | 'isLog': true 453 | }, 454 | { 455 | 'tracer': { 456 | 'from': { 457 | 'name': 'servicea' 458 | }, 459 | 'to': { 460 | 'name': 'kafka' 461 | }, 462 | 'action': 'send', 463 | 'traceId': '0562809467078eab', 464 | 'durationMs': 3.268, 465 | 'parentSpanId': '5141af930f03274e', 466 | 'spanId': '79145dc8245c2899', 467 | 'timestamp': 1541405397414201, 468 | 'direction': 2 469 | }, 470 | 'traceId': '0562809467078eab', 471 | 'parentId': '5141af930f03274e', 472 | 'id': '79145dc8245c2899', 473 | 'kind': 'PRODUCER', 474 | 'name': 'send', 475 | 'timestamp': 1541405397414201, 476 | 'duration': 3268, 477 | 'localEndpoint': { 478 | 'serviceName': 'servicea' 479 | }, 480 | 'remoteEndpoint': { 481 | 'serviceName': 'kafka' 482 | }, 483 | 'tags': { 484 | 'kafka.topic': 'command-messages' 485 | } 486 | }, 487 | { 488 | 'tracer': { 489 | 'from': { 490 | 'name': 'servicea' 491 | }, 492 | 'to': { 493 | 'name': 'kafka' 494 | }, 495 | 'action': 'send', 496 | 'traceId': '0562809467078eab', 497 | 'durationMs': 1.471, 498 | 'parentSpanId': '09fdf808ef0d60c0', 499 | 'spanId': '2e22a0564764943a', 500 | 'timestamp': 1541405397450652, 501 | 'direction': 2 502 | }, 503 | 'traceId': '0562809467078eab', 504 | 'parentId': '09fdf808ef0d60c0', 505 | 'id': '2e22a0564764943a', 506 | 'kind': 'PRODUCER', 507 | 'name': 'send', 508 | 'timestamp': 1541405397450652, 509 | 'duration': 1471, 510 | 'localEndpoint': { 511 | 'serviceName': 'servicea' 512 | }, 513 | 'remoteEndpoint': { 514 | 'serviceName': 'kafka' 515 | }, 516 | 'tags': { 517 | 'kafka.key': 'e1b2f8b5-dd1e-11e8-93d1-0050568b53f5', 518 | 'kafka.topic': 'messages' 519 | } 520 | }, 521 | { 522 | 'tracer': { 523 | 'from': { 524 | 'name': 'servicea' 525 | }, 526 | 'to': { 527 | 'name': 'kafka' 528 | }, 529 | 'action': 'send', 530 | 'traceId': '0562809467078eab', 531 | 'durationMs': 102.48, 532 | 'parentSpanId': '09fdf808ef0d60c0', 533 | 'spanId': '26bd982d53f50d1f', 534 | 'timestamp': 1541405397451227, 535 | 'direction': 2 536 | }, 537 | 'traceId': '0562809467078eab', 538 | 'parentId': '09fdf808ef0d60c0', 539 | 'id': '26bd982d53f50d1f', 540 | 'kind': 'PRODUCER', 541 | 'name': 'send', 542 | 'timestamp': 1541405397451227, 543 | 'duration': 102480, 544 | 'localEndpoint': { 545 | 'serviceName': 'servicea' 546 | }, 547 | 'remoteEndpoint': { 548 | 'serviceName': 'kafka' 549 | }, 550 | 'tags': { 551 | 'kafka.key': 'e1b2f8b5-dd1e-11e8-93d1-0050568b53f5', 552 | 'kafka.topic': 'central.messages' 553 | } 554 | }, 555 | { 556 | 'tracer': { 557 | 'from': { 558 | 'name': 'servicea' 559 | }, 560 | 'to': { 561 | 'name': 'servicea' 562 | }, 563 | 'action': 'on-message', 564 | 'traceId': '0562809467078eab', 565 | 'durationMs': 223.097, 566 | 'parentSpanId': '0562809467078eab', 567 | 'spanId': 'e73d7bc0194a76e0', 568 | 'timestamp': 1541405397452002, 569 | 'direction': 2 570 | }, 571 | 'traceId': '0562809467078eab', 572 | 'parentId': '0562809467078eab', 573 | 'id': 'e73d7bc0194a76e0', 574 | 'name': 'on-message', 575 | 'timestamp': 1541405397452002, 576 | 'duration': 223097, 577 | 'localEndpoint': { 578 | 'serviceName': 'servicea' 579 | }, 580 | 'isLog': true 581 | }, 582 | { 583 | 'tracer': { 584 | 'from': { 585 | 'name': 'serviceb' 586 | }, 587 | 'to': { 588 | 'name': 'kafka' 589 | }, 590 | 'action': 'poll', 591 | 'traceId': '0562809467078eab', 592 | 'durationMs': 0.001, 593 | 'parentSpanId': '2e22a0564764943a', 594 | 'spanId': 'b4a2a87ee7f003bc', 595 | 'timestamp': 1541405397452008, 596 | 'direction': 3 597 | }, 598 | 'traceId': '0562809467078eab', 599 | 'parentId': '2e22a0564764943a', 600 | 'id': 'b4a2a87ee7f003bc', 601 | 'kind': 'CONSUMER', 602 | 'name': 'poll', 603 | 'timestamp': 1541405397452008, 604 | 'duration': 1, 605 | 'localEndpoint': { 606 | 'serviceName': 'serviceb' 607 | }, 608 | 'remoteEndpoint': { 609 | 'serviceName': 'kafka' 610 | }, 611 | 'tags': { 612 | 'kafka.topic': 'messages' 613 | } 614 | }, 615 | { 616 | 'tracer': { 617 | 'from': { 618 | 'name': 'serviceb' 619 | }, 620 | 'to': { 621 | 'name': 'serviceb' 622 | }, 623 | 'action': 'on-message', 624 | 'traceId': '0562809467078eab', 625 | 'durationMs': 0.31, 626 | 'parentSpanId': 'b4a2a87ee7f003bc', 627 | 'spanId': '2f77d5b0b8e0de35', 628 | 'timestamp': 1541405397453001, 629 | 'direction': 2 630 | }, 631 | 'traceId': '0562809467078eab', 632 | 'parentId': 'b4a2a87ee7f003bc', 633 | 'id': '2f77d5b0b8e0de35', 634 | 'name': 'on-message', 635 | 'timestamp': 1541405397453001, 636 | 'duration': 310, 637 | 'localEndpoint': { 638 | 'serviceName': 'serviceb' 639 | }, 640 | 'tags': { 641 | 'error': 'some error' 642 | }, 643 | 'isLog': true 644 | }, 645 | { 646 | 'tracer': { 647 | 'from': { 648 | 'name': 'servicea' 649 | }, 650 | 'to': { 651 | 'name': 'servicea' 652 | }, 653 | 'action': 'handle', 654 | 'traceId': '0562809467078eab', 655 | 'durationMs': 1.255, 656 | 'parentSpanId': 'e73d7bc0194a76e0', 657 | 'spanId': '556955e15da195f5', 658 | 'timestamp': 1541405397655018, 659 | 'direction': 2 660 | }, 661 | 'traceId': '0562809467078eab', 662 | 'parentId': 'e73d7bc0194a76e0', 663 | 'id': '556955e15da195f5', 664 | 'name': 'handle', 665 | 'timestamp': 1541405397655018, 666 | 'duration': 1255, 667 | 'localEndpoint': { 668 | 'serviceName': 'servicea' 669 | }, 670 | 'tags': { 671 | 'class': 'Projector', 672 | 'method': 'handle' 673 | }, 674 | 'isLog': true 675 | }, 676 | { 677 | 'tracer': { 678 | 'from': { 679 | 'name': 'servicea' 680 | }, 681 | 'to': { 682 | 'name': 'kafka' 683 | }, 684 | 'action': 'send', 685 | 'traceId': '0562809467078eab', 686 | 'durationMs': 0.937, 687 | 'parentSpanId': '556955e15da195f5', 688 | 'spanId': 'e2feae695b1d1a6a', 689 | 'timestamp': 1541405397655722, 690 | 'direction': 2 691 | }, 692 | 'traceId': '0562809467078eab', 693 | 'parentId': '556955e15da195f5', 694 | 'id': 'e2feae695b1d1a6a', 695 | 'kind': 'PRODUCER', 696 | 'name': 'send', 697 | 'timestamp': 1541405397655722, 698 | 'duration': 937, 699 | 'localEndpoint': { 700 | 'serviceName': 'servicea' 701 | }, 702 | 'remoteEndpoint': { 703 | 'serviceName': 'kafka' 704 | }, 705 | 'tags': { 706 | 'kafka.topic': 'command-messages' 707 | } 708 | }, 709 | { 710 | 'tracer': { 711 | 'from': { 712 | 'name': 'servicea' 713 | }, 714 | 'to': { 715 | 'name': 'kafka' 716 | }, 717 | 'action': 'send', 718 | 'traceId': '0562809467078eab', 719 | 'durationMs': 1.035, 720 | 'parentSpanId': 'e73d7bc0194a76e0', 721 | 'spanId': '5133caca649f802b', 722 | 'timestamp': 1541405397674703, 723 | 'direction': 2 724 | }, 725 | 'traceId': '0562809467078eab', 726 | 'parentId': 'e73d7bc0194a76e0', 727 | 'id': '5133caca649f802b', 728 | 'kind': 'PRODUCER', 729 | 'name': 'send', 730 | 'timestamp': 1541405397674703, 731 | 'duration': 1035, 732 | 'localEndpoint': { 733 | 'serviceName': 'servicea' 734 | }, 735 | 'remoteEndpoint': { 736 | 'serviceName': 'kafka' 737 | }, 738 | 'tags': { 739 | 'kafka.key': 'e1b2f8b5-dd1e-11e8-93d1-0050568b53f5', 740 | 'kafka.topic': 'messages' 741 | } 742 | }, 743 | { 744 | 'tracer': { 745 | 'from': { 746 | 'name': 'servicea' 747 | }, 748 | 'to': { 749 | 'name': 'kafka' 750 | }, 751 | 'action': 'send', 752 | 'traceId': '0562809467078eab', 753 | 'durationMs': 0.957, 754 | 'parentSpanId': 'e73d7bc0194a76e0', 755 | 'spanId': 'b7808dc69c7726f2', 756 | 'timestamp': 1541405397674873, 757 | 'direction': 2 758 | }, 759 | 'traceId': '0562809467078eab', 760 | 'parentId': 'e73d7bc0194a76e0', 761 | 'id': 'b7808dc69c7726f2', 762 | 'kind': 'PRODUCER', 763 | 'name': 'send', 764 | 'timestamp': 1541405397674873, 765 | 'duration': 957, 766 | 'localEndpoint': { 767 | 'serviceName': 'servicea' 768 | }, 769 | 'remoteEndpoint': { 770 | 'serviceName': 'kafka' 771 | }, 772 | 'tags': { 773 | 'kafka.key': 'e1b2f8b5-dd1e-11e8-93d1-0050568b53f5', 774 | 'kafka.topic': 'central.messages' 775 | } 776 | }, 777 | { 778 | 'tracer': { 779 | 'from': { 780 | 'name': 'servicea' 781 | }, 782 | 'to': { 783 | 'name': 'servicea' 784 | }, 785 | 'action': 'on-message', 786 | 'traceId': '0562809467078eab', 787 | 'durationMs': 171.798, 788 | 'parentSpanId': '0562809467078eab', 789 | 'spanId': 'd1b4806e7e9eed25', 790 | 'timestamp': 1541405397675002, 791 | 'direction': 2 792 | }, 793 | 'traceId': '0562809467078eab', 794 | 'parentId': '0562809467078eab', 795 | 'id': 'd1b4806e7e9eed25', 796 | 'name': 'on-message', 797 | 'timestamp': 1541405397675002, 798 | 'duration': 171798, 799 | 'localEndpoint': { 800 | 'serviceName': 'servicea' 801 | }, 802 | 'isLog': true 803 | }, 804 | { 805 | 'tracer': { 806 | 'from': { 807 | 'name': 'serviceb' 808 | }, 809 | 'to': { 810 | 'name': 'serviceb' 811 | }, 812 | 'action': 'on-message', 813 | 'traceId': '0562809467078eab', 814 | 'durationMs': 0.265, 815 | 'parentSpanId': 'a5524eec8147b1da', 816 | 'spanId': '4a64a63e4b58e665', 817 | 'timestamp': 1541405397676001, 818 | 'direction': 2 819 | }, 820 | 'traceId': '0562809467078eab', 821 | 'parentId': 'a5524eec8147b1da', 822 | 'id': '4a64a63e4b58e665', 823 | 'name': 'on-message', 824 | 'timestamp': 1541405397676001, 825 | 'duration': 265, 826 | 'localEndpoint': { 827 | 'serviceName': 'serviceb' 828 | }, 829 | 'tags': { 830 | 'error': 'some error' 831 | }, 832 | 'isLog': true 833 | }, 834 | { 835 | 'tracer': { 836 | 'from': { 837 | 'name': 'serviceb' 838 | }, 839 | 'to': { 840 | 'name': 'kafka' 841 | }, 842 | 'action': 'poll', 843 | 'traceId': '0562809467078eab', 844 | 'durationMs': 0.001, 845 | 'parentSpanId': '5133caca649f802b', 846 | 'spanId': 'a5524eec8147b1da', 847 | 'timestamp': 1541405397676009, 848 | 'direction': 3 849 | }, 850 | 'traceId': '0562809467078eab', 851 | 'parentId': '5133caca649f802b', 852 | 'id': 'a5524eec8147b1da', 853 | 'kind': 'CONSUMER', 854 | 'name': 'poll', 855 | 'timestamp': 1541405397676009, 856 | 'duration': 1, 857 | 'localEndpoint': { 858 | 'serviceName': 'serviceb' 859 | }, 860 | 'remoteEndpoint': { 861 | 'serviceName': 'kafka' 862 | }, 863 | 'tags': { 864 | 'kafka.topic': 'messages' 865 | } 866 | }, 867 | { 868 | 'tracer': { 869 | 'from': { 870 | 'name': 'servicea' 871 | }, 872 | 'to': { 873 | 'name': 'servicea' 874 | }, 875 | 'action': 'handle', 876 | 'traceId': '0562809467078eab', 877 | 'durationMs': 0.774, 878 | 'parentSpanId': 'd1b4806e7e9eed25', 879 | 'spanId': '97a1159e0eb74759', 880 | 'timestamp': 1541405397827569, 881 | 'direction': 2 882 | }, 883 | 'traceId': '0562809467078eab', 884 | 'parentId': 'd1b4806e7e9eed25', 885 | 'id': '97a1159e0eb74759', 886 | 'name': 'handle', 887 | 'timestamp': 1541405397827569, 888 | 'duration': 774, 889 | 'localEndpoint': { 890 | 'serviceName': 'servicea' 891 | }, 892 | 'tags': { 893 | 'class': 'Projector', 894 | 'method': 'handle' 895 | }, 896 | 'isLog': true 897 | }, 898 | { 899 | 'tracer': { 900 | 'from': { 901 | 'name': 'servicea' 902 | }, 903 | 'to': { 904 | 'name': 'kafka' 905 | }, 906 | 'action': 'send', 907 | 'traceId': '0562809467078eab', 908 | 'durationMs': 0.746, 909 | 'parentSpanId': '97a1159e0eb74759', 910 | 'spanId': 'b42b60c5c19ce714', 911 | 'timestamp': 1541405397827998, 912 | 'direction': 2 913 | }, 914 | 'traceId': '0562809467078eab', 915 | 'parentId': '97a1159e0eb74759', 916 | 'id': 'b42b60c5c19ce714', 917 | 'kind': 'PRODUCER', 918 | 'name': 'send', 919 | 'timestamp': 1541405397827998, 920 | 'duration': 746, 921 | 'localEndpoint': { 922 | 'serviceName': 'servicea' 923 | }, 924 | 'remoteEndpoint': { 925 | 'serviceName': 'kafka' 926 | }, 927 | 'tags': { 928 | 'kafka.topic': 'command-messages' 929 | } 930 | }, 931 | { 932 | 'tracer': { 933 | 'from': { 934 | 'name': 'servicea' 935 | }, 936 | 'to': { 937 | 'name': 'kafka' 938 | }, 939 | 'action': 'send', 940 | 'traceId': '0562809467078eab', 941 | 'durationMs': 1.259, 942 | 'parentSpanId': 'd1b4806e7e9eed25', 943 | 'spanId': '085a9df516127e50', 944 | 'timestamp': 1541405397846422, 945 | 'direction': 2 946 | }, 947 | 'traceId': '0562809467078eab', 948 | 'parentId': 'd1b4806e7e9eed25', 949 | 'id': '085a9df516127e50', 950 | 'kind': 'PRODUCER', 951 | 'name': 'send', 952 | 'timestamp': 1541405397846422, 953 | 'duration': 1259, 954 | 'localEndpoint': { 955 | 'serviceName': 'servicea' 956 | }, 957 | 'remoteEndpoint': { 958 | 'serviceName': 'kafka' 959 | }, 960 | 'tags': { 961 | 'kafka.key': 'e1b2f8b5-dd1e-11e8-93d1-0050568b53f5', 962 | 'kafka.topic': 'messages' 963 | } 964 | }, 965 | { 966 | 'tracer': { 967 | 'from': { 968 | 'name': 'servicea' 969 | }, 970 | 'to': { 971 | 'name': 'kafka' 972 | }, 973 | 'action': 'send', 974 | 'traceId': '0562809467078eab', 975 | 'durationMs': 0.803, 976 | 'parentSpanId': 'd1b4806e7e9eed25', 977 | 'spanId': '5bc03264c704f245', 978 | 'timestamp': 1541405397846576, 979 | 'direction': 2 980 | }, 981 | 'traceId': '0562809467078eab', 982 | 'parentId': 'd1b4806e7e9eed25', 983 | 'id': '5bc03264c704f245', 984 | 'kind': 'PRODUCER', 985 | 'name': 'send', 986 | 'timestamp': 1541405397846576, 987 | 'duration': 803, 988 | 'localEndpoint': { 989 | 'serviceName': 'servicea' 990 | }, 991 | 'remoteEndpoint': { 992 | 'serviceName': 'kafka' 993 | }, 994 | 'tags': { 995 | 'kafka.key': 'e1b2f8b5-dd1e-11e8-93d1-0050568b53f5', 996 | 'kafka.topic': 'central.messages' 997 | } 998 | }, 999 | { 1000 | 'tracer': { 1001 | 'from': { 1002 | 'name': 'servicea' 1003 | }, 1004 | 'to': { 1005 | 'name': 'kafka' 1006 | }, 1007 | 'action': 'poll', 1008 | 'traceId': '0562809467078eab', 1009 | 'durationMs': 0.001, 1010 | 'parentSpanId': '2e22a0564764943a', 1011 | 'spanId': 'e0473dc3d8732e8b', 1012 | 'timestamp': 1541405397847007, 1013 | 'direction': 3 1014 | }, 1015 | 'traceId': '0562809467078eab', 1016 | 'parentId': '2e22a0564764943a', 1017 | 'id': 'e0473dc3d8732e8b', 1018 | 'kind': 'CONSUMER', 1019 | 'name': 'poll', 1020 | 'timestamp': 1541405397847007, 1021 | 'duration': 1, 1022 | 'localEndpoint': { 1023 | 'serviceName': 'servicea' 1024 | }, 1025 | 'remoteEndpoint': { 1026 | 'serviceName': 'kafka' 1027 | }, 1028 | 'tags': { 1029 | 'kafka.topic': 'messages' 1030 | } 1031 | }, 1032 | { 1033 | 'tracer': { 1034 | 'from': { 1035 | 'name': 'serviceb' 1036 | }, 1037 | 'to': { 1038 | 'name': 'serviceb' 1039 | }, 1040 | 'action': 'on-message', 1041 | 'traceId': '0562809467078eab', 1042 | 'durationMs': 0.251, 1043 | 'parentSpanId': '519d27c657334615', 1044 | 'spanId': '568b33e6af8a225a', 1045 | 'timestamp': 1541405397848001, 1046 | 'direction': 2 1047 | }, 1048 | 'traceId': '0562809467078eab', 1049 | 'parentId': '519d27c657334615', 1050 | 'id': '568b33e6af8a225a', 1051 | 'name': 'on-message', 1052 | 'timestamp': 1541405397848001, 1053 | 'duration': 251, 1054 | 'localEndpoint': { 1055 | 'serviceName': 'serviceb' 1056 | }, 1057 | 'tags': { 1058 | 'error': '' 1059 | }, 1060 | 'isLog': true 1061 | }, 1062 | { 1063 | 'tracer': { 1064 | 'from': { 1065 | 'name': 'servicea' 1066 | }, 1067 | 'to': { 1068 | 'name': 'kafka' 1069 | }, 1070 | 'action': 'poll', 1071 | 'traceId': '0562809467078eab', 1072 | 'durationMs': 0.001, 1073 | 'parentSpanId': '085a9df516127e50', 1074 | 'spanId': 'bad2ddec9d33837b', 1075 | 'timestamp': 1541405397848002, 1076 | 'direction': 3 1077 | }, 1078 | 'traceId': '0562809467078eab', 1079 | 'parentId': '085a9df516127e50', 1080 | 'id': 'bad2ddec9d33837b', 1081 | 'kind': 'CONSUMER', 1082 | 'name': 'poll', 1083 | 'timestamp': 1541405397848002, 1084 | 'duration': 1, 1085 | 'localEndpoint': { 1086 | 'serviceName': 'servicea' 1087 | }, 1088 | 'remoteEndpoint': { 1089 | 'serviceName': 'kafka' 1090 | }, 1091 | 'tags': { 1092 | 'kafka.topic': 'messages' 1093 | } 1094 | }, 1095 | { 1096 | 'tracer': { 1097 | 'from': { 1098 | 'name': 'servicea' 1099 | }, 1100 | 'to': { 1101 | 'name': 'servicea' 1102 | }, 1103 | 'action': 'on-message', 1104 | 'traceId': '0562809467078eab', 1105 | 'durationMs': 0.149, 1106 | 'parentSpanId': 'e0473dc3d8732e8b', 1107 | 'spanId': 'd77b1306b69e39a5', 1108 | 'timestamp': 1541405397848002, 1109 | 'direction': 2 1110 | }, 1111 | 'traceId': '0562809467078eab', 1112 | 'parentId': 'e0473dc3d8732e8b', 1113 | 'id': 'd77b1306b69e39a5', 1114 | 'name': 'on-message', 1115 | 'timestamp': 1541405397848002, 1116 | 'duration': 149, 1117 | 'localEndpoint': { 1118 | 'serviceName': 'servicea' 1119 | }, 1120 | 'isLog': true 1121 | }, 1122 | { 1123 | 'tracer': { 1124 | 'from': { 1125 | 'name': 'servicea' 1126 | }, 1127 | 'to': { 1128 | 'name': 'kafka' 1129 | }, 1130 | 'action': 'poll', 1131 | 'traceId': '0562809467078eab', 1132 | 'durationMs': 0.001, 1133 | 'parentSpanId': '5133caca649f802b', 1134 | 'spanId': '08e980b0e786309b', 1135 | 'timestamp': 1541405397848003, 1136 | 'direction': 3 1137 | }, 1138 | 'traceId': '0562809467078eab', 1139 | 'parentId': '5133caca649f802b', 1140 | 'id': '08e980b0e786309b', 1141 | 'kind': 'CONSUMER', 1142 | 'name': 'poll', 1143 | 'timestamp': 1541405397848003, 1144 | 'duration': 1, 1145 | 'localEndpoint': { 1146 | 'serviceName': 'servicea' 1147 | }, 1148 | 'remoteEndpoint': { 1149 | 'serviceName': 'kafka' 1150 | }, 1151 | 'tags': { 1152 | 'kafka.topic': 'messages' 1153 | } 1154 | }, 1155 | { 1156 | 'tracer': { 1157 | 'from': { 1158 | 'name': 'serviceb' 1159 | }, 1160 | 'to': { 1161 | 'name': 'kafka' 1162 | }, 1163 | 'action': 'poll', 1164 | 'traceId': '0562809467078eab', 1165 | 'durationMs': 0.001, 1166 | 'parentSpanId': '085a9df516127e50', 1167 | 'spanId': '519d27c657334615', 1168 | 'timestamp': 1541405397848005, 1169 | 'direction': 3 1170 | }, 1171 | 'traceId': '0562809467078eab', 1172 | 'parentId': '085a9df516127e50', 1173 | 'id': '519d27c657334615', 1174 | 'kind': 'CONSUMER', 1175 | 'name': 'poll', 1176 | 'timestamp': 1541405397848005, 1177 | 'duration': 1, 1178 | 'localEndpoint': { 1179 | 'serviceName': 'serviceb' 1180 | }, 1181 | 'remoteEndpoint': { 1182 | 'serviceName': 'kafka' 1183 | }, 1184 | 'tags': { 1185 | 'kafka.topic': 'messages' 1186 | } 1187 | }, 1188 | { 1189 | 'tracer': { 1190 | 'from': { 1191 | 'name': 'servicea' 1192 | }, 1193 | 'to': { 1194 | 'name': 'servicea' 1195 | }, 1196 | 'action': 'on-message', 1197 | 'traceId': '0562809467078eab', 1198 | 'durationMs': 0.045, 1199 | 'parentSpanId': 'bad2ddec9d33837b', 1200 | 'spanId': 'd5d4778b19cf7e93', 1201 | 'timestamp': 1541405397849001, 1202 | 'direction': 2 1203 | }, 1204 | 'traceId': '0562809467078eab', 1205 | 'parentId': 'bad2ddec9d33837b', 1206 | 'id': 'd5d4778b19cf7e93', 1207 | 'name': 'on-message', 1208 | 'timestamp': 1541405397849001, 1209 | 'duration': 45, 1210 | 'localEndpoint': { 1211 | 'serviceName': 'servicea' 1212 | }, 1213 | 'isLog': true 1214 | }, 1215 | { 1216 | 'tracer': { 1217 | 'from': { 1218 | 'name': 'servicea' 1219 | }, 1220 | 'to': { 1221 | 'name': 'servicea' 1222 | }, 1223 | 'action': 'on-message', 1224 | 'traceId': '0562809467078eab', 1225 | 'durationMs': 0.065, 1226 | 'parentSpanId': '08e980b0e786309b', 1227 | 'spanId': '630c1215f2e430a7', 1228 | 'timestamp': 1541405397849002, 1229 | 'direction': 2 1230 | }, 1231 | 'traceId': '0562809467078eab', 1232 | 'parentId': '08e980b0e786309b', 1233 | 'id': '630c1215f2e430a7', 1234 | 'name': 'on-message', 1235 | 'timestamp': 1541405397849002, 1236 | 'duration': 65, 1237 | 'localEndpoint': { 1238 | 'serviceName': 'servicea' 1239 | }, 1240 | 'isLog': true 1241 | } 1242 | ] 1243 | 1244 | } 1245 | , 1246 | { 1247 | traceId: 'ef86c83c0a05a6d6', 1248 | name:'ascend.json', 1249 | result: [ 1250 | { 1251 | 'tracer': { 1252 | 'from': { 1253 | 'name': 'mobile-gateway' 1254 | }, 1255 | 'to': { 1256 | 'name': '110.170.201.178' 1257 | }, 1258 | 'action': 'get', 1259 | 'traceId': 'ef86c83c0a05a6d6', 1260 | 'durationMs': 38.793, 1261 | 'spanId': 'ef86c83c0a05a6d6', 1262 | 'timestamp': 1531282088533035, 1263 | 'direction': 1 1264 | }, 1265 | 'traceId': 'ef86c83c0a05a6d6', 1266 | 'id': 'ef86c83c0a05a6d6', 1267 | 'kind': 'SERVER', 1268 | 'name': 'get', 1269 | 'timestamp': 1531282088533035, 1270 | 'duration': 38793, 1271 | 'localEndpoint': { 1272 | 'serviceName': 'mobile-gateway', 1273 | 'ipv4': '172.17.0.13' 1274 | }, 1275 | 'remoteEndpoint': { 1276 | 'ipv4': '110.170.201.178', 1277 | 'port': 39308 1278 | }, 1279 | 'tags': { 1280 | 'http.method': 'GET', 1281 | 'http.path': '/mobile-gateway/content/personal' 1282 | } 1283 | }, 1284 | { 1285 | 'tracer': { 1286 | 'from': { 1287 | 'name': 'mobile-gateway' 1288 | }, 1289 | 'to': {}, 1290 | 'action': 'get', 1291 | 'traceId': 'ef86c83c0a05a6d6', 1292 | 'durationMs': 14.077, 1293 | 'parentSpanId': 'ef86c83c0a05a6d6', 1294 | 'spanId': '52b1ab4956917c39', 1295 | 'timestamp': 1531282088533834, 1296 | 'direction': 0 1297 | }, 1298 | 'traceId': 'ef86c83c0a05a6d6', 1299 | 'parentId': 'ef86c83c0a05a6d6', 1300 | 'id': '52b1ab4956917c39', 1301 | 'kind': 'CLIENT', 1302 | 'name': 'get', 1303 | 'timestamp': 1531282088533834, 1304 | 'duration': 14077, 1305 | 'localEndpoint': { 1306 | 'serviceName': 'mobile-gateway', 1307 | 'ipv4': '172.17.0.13' 1308 | }, 1309 | 'tags': { 1310 | 'http.method': 'GET', 1311 | 'http.path': '/auth-service/v1/authentications/' 1312 | } 1313 | }, 1314 | { 1315 | 'tracer': { 1316 | 'from': { 1317 | 'name': 'mobile-gateway' 1318 | }, 1319 | 'to': {}, 1320 | 'action': 'get', 1321 | 'traceId': 'ef86c83c0a05a6d6', 1322 | 'durationMs': 20.333, 1323 | 'parentSpanId': 'ef86c83c0a05a6d6', 1324 | 'spanId': 'ecc00062ceef4bf0', 1325 | 'timestamp': 1531282088548312, 1326 | 'direction': 0 1327 | }, 1328 | 'traceId': 'ef86c83c0a05a6d6', 1329 | 'parentId': 'ef86c83c0a05a6d6', 1330 | 'id': 'ecc00062ceef4bf0', 1331 | 'kind': 'CLIENT', 1332 | 'name': 'get', 1333 | 'timestamp': 1531282088548312, 1334 | 'duration': 20333, 1335 | 'localEndpoint': { 1336 | 'serviceName': 'mobile-gateway', 1337 | 'ipv4': '172.17.0.13' 1338 | }, 1339 | 'tags': { 1340 | 'http.method': 'GET', 1341 | 'http.path': '/content/personal' 1342 | } 1343 | }, 1344 | { 1345 | 'tracer': { 1346 | 'from': { 1347 | 'name': 'content-service' 1348 | }, 1349 | 'to': { 1350 | 'name': '10.230.16.208' 1351 | }, 1352 | 'action': 'get /content/personal', 1353 | 'traceId': 'ef86c83c0a05a6d6', 1354 | 'durationMs': 15.888, 1355 | 'parentSpanId': 'ef86c83c0a05a6d6', 1356 | 'spanId': 'ecc00062ceef4bf0', 1357 | 'timestamp': 1531282088552037, 1358 | 'direction': 1 1359 | }, 1360 | 'traceId': 'ef86c83c0a05a6d6', 1361 | 'parentId': 'ef86c83c0a05a6d6', 1362 | 'id': 'ecc00062ceef4bf0', 1363 | 'kind': 'SERVER', 1364 | 'name': 'get /content/personal', 1365 | 'timestamp': 1531282088552037, 1366 | 'duration': 15888, 1367 | 'localEndpoint': { 1368 | 'serviceName': 'content-service', 1369 | 'ipv4': '172.17.0.10' 1370 | }, 1371 | 'remoteEndpoint': { 1372 | 'ipv4': '10.230.16.208', 1373 | 'port': 3400 1374 | }, 1375 | 'tags': { 1376 | 'http.method': 'GET', 1377 | 'http.path': '/content/personal', 1378 | 'mvc.controller.class': 'GenericController', 1379 | 'mvc.controller.method': 'getPersonalGeneric' 1380 | }, 1381 | 'shared': true 1382 | }, 1383 | { 1384 | 'tracer': { 1385 | 'from': { 1386 | 'name': 'auth-service' 1387 | }, 1388 | 'to': {}, 1389 | 'action': 'http:/parent/auth-service/v1/authentications/', 1390 | 'traceId': 'ef86c83c0a05a6d6', 1391 | 'durationMs': 4.157, 1392 | 'parentSpanId': 'ef86c83c0a05a6d6', 1393 | 'spanId': '52b1ab4956917c39', 1394 | 'timestamp': 1531282088553000, 1395 | 'direction': 1 1396 | }, 1397 | 'traceId': 'ef86c83c0a05a6d6', 1398 | 'parentId': 'ef86c83c0a05a6d6', 1399 | 'id': '52b1ab4956917c39', 1400 | 'kind': 'SERVER', 1401 | 'name': 'http:/parent/auth-service/v1/authentications/', 1402 | 'timestamp': 1531282088553000, 1403 | 'duration': 4157, 1404 | 'localEndpoint': { 1405 | 'serviceName': 'auth-service', 1406 | 'ipv4': '172.17.0.9', 1407 | 'port': 8080 1408 | }, 1409 | 'tags': { 1410 | 'http.host': 'auth-service-alp', 1411 | 'http.method': 'GET', 1412 | 'http.path': '/auth-service/v1/authentications/', 1413 | 'http.url': 'http://auth-service-alp/auth-service/v1/authentications/', 1414 | 'mvc.controller.class': 'AuthenticationController', 1415 | 'mvc.controller.method': 'authenticationsWithAuthorizationHeader', 1416 | 'spring.instance_id': 'bb52cfc01466:auth-service' 1417 | }, 1418 | 'shared': true 1419 | }, 1420 | { 1421 | 'tracer': { 1422 | 'from': { 1423 | 'name': 'auth-service' 1424 | }, 1425 | 'to': { 1426 | 'name': 'auth-service' 1427 | }, 1428 | 'action': 'extend-token-life', 1429 | 'traceId': 'ef86c83c0a05a6d6', 1430 | 'durationMs': 1.291, 1431 | 'parentSpanId': '52b1ab4956917c39', 1432 | 'spanId': '80c0d1d62f437a1b', 1433 | 'timestamp': 1531282088553000, 1434 | 'direction': 2 1435 | }, 1436 | 'traceId': 'ef86c83c0a05a6d6', 1437 | 'parentId': '52b1ab4956917c39', 1438 | 'id': '80c0d1d62f437a1b', 1439 | 'name': 'extend-token-life', 1440 | 'timestamp': 1531282088553000, 1441 | 'duration': 1291, 1442 | 'localEndpoint': { 1443 | 'serviceName': 'auth-service', 1444 | 'ipv4': '172.17.0.9', 1445 | 'port': 8080 1446 | }, 1447 | 'tags': { 1448 | 'class': 'AuthenticationService', 1449 | 'lc': 'async', 1450 | 'method': 'extendToken' 1451 | }, 1452 | 'isLog': true 1453 | }, 1454 | { 1455 | 'tracer': { 1456 | 'from': { 1457 | 'name': 'content-service' 1458 | }, 1459 | 'to': { 1460 | 'name': 'content-service' 1461 | }, 1462 | 'action': 'hystrix', 1463 | 'traceId': 'ef86c83c0a05a6d6', 1464 | 'durationMs': 0.863, 1465 | 'parentSpanId': 'ecc00062ceef4bf0', 1466 | 'spanId': 'e6422f7ff7d78099', 1467 | 'timestamp': 1531282088555338, 1468 | 'direction': 2 1469 | }, 1470 | 'traceId': 'ef86c83c0a05a6d6', 1471 | 'parentId': 'ecc00062ceef4bf0', 1472 | 'id': 'e6422f7ff7d78099', 1473 | 'name': 'hystrix', 1474 | 'timestamp': 1531282088555338, 1475 | 'duration': 863, 1476 | 'localEndpoint': { 1477 | 'serviceName': 'content-service', 1478 | 'ipv4': '172.17.0.10' 1479 | }, 1480 | 'isLog': true 1481 | }, 1482 | { 1483 | 'tracer': { 1484 | 'from': { 1485 | 'name': 'content-service' 1486 | }, 1487 | 'to': {}, 1488 | 'action': 'get', 1489 | 'traceId': 'ef86c83c0a05a6d6', 1490 | 'durationMs': 8.965, 1491 | 'parentSpanId': 'ecc00062ceef4bf0', 1492 | 'spanId': 'c21c6c51ac71b3ba', 1493 | 'timestamp': 1531282088556600, 1494 | 'direction': 0 1495 | }, 1496 | 'traceId': 'ef86c83c0a05a6d6', 1497 | 'parentId': 'ecc00062ceef4bf0', 1498 | 'id': 'c21c6c51ac71b3ba', 1499 | 'kind': 'CLIENT', 1500 | 'name': 'get', 1501 | 'timestamp': 1531282088556600, 1502 | 'duration': 8965, 1503 | 'localEndpoint': { 1504 | 'serviceName': 'content-service', 1505 | 'ipv4': '172.17.0.10' 1506 | }, 1507 | 'tags': { 1508 | 'http.method': 'GET', 1509 | 'http.path': '/api/v1/banners/10000001502' 1510 | } 1511 | } 1512 | ] 1513 | 1514 | } 1515 | ] ; 1516 | } 1517 | --------------------------------------------------------------------------------